Merge pull request #1639 from Icinga/feature/timeperiod-include-exclude

Add support for Timeperiod include/exclude
This commit is contained in:
Thomas Gelf 2018-09-17 09:10:59 +02:00 committed by GitHub
commit bf72c81062
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 388 additions and 25 deletions

View File

@ -52,8 +52,54 @@ class IcingaTimePeriodForm extends DirectorObjectForm
$this->addHidden('update_method', 'LegacyTimePeriod'); $this->addHidden('update_method', 'LegacyTimePeriod');
} }
$this->addIncludeExclude();
$this->addImportsElement(); $this->addImportsElement();
$this->setButtons(); $this->setButtons();
} }
protected function addIncludeExclude()
{
$periods = [];
foreach ($this->db->enumTimeperiods() as $id => $period) {
if ($this->object === null || $this->object->get('object_name') !== $period) {
$periods[$period] = $period;
}
}
if (empty($periods)) {
return;
}
$this->addElement(
'extensibleSet',
'includes',
array(
'label' => $this->translate('Include period'),
'description' => $this->translate(
'Include other time periods into this.'
),
'multiOptions' => $this->optionalEnum($periods),
)
);
$this->addElement(
'extensibleSet',
'excludes',
array(
'label' => $this->translate('Exclude period'),
'description' => $this->translate(
'Exclude other time periods from this.'
),
'multiOptions' => $this->optionalEnum($periods),
)
);
$this->optionalBoolean(
'prefer_includes',
$this->translate('Prefer includes'),
$this->translate('Whether to prefer timeperiods includes or excludes. Default to true.')
);
}
} }

View File

@ -2176,6 +2176,19 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $out; return $out;
} }
/**
* @return string
*/
protected function renderLegacyMultiRelations()
{
$out = '';
foreach ($this->loadAllMultiRelations() as $rel) {
$out .= $rel->toLegacyConfigString();
}
return $out;
}
/** /**
* @return string * @return string
*/ */
@ -2393,11 +2406,11 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
$this->renderLegacyObjectHeader(), $this->renderLegacyObjectHeader(),
$this->renderLegacyImports(), $this->renderLegacyImports(),
$this->renderLegacyProperties(), $this->renderLegacyProperties(),
$this->renderLegacyRanges(),
//$this->renderArguments(), //$this->renderArguments(),
//$this->renderRelatedSets(), //$this->renderRelatedSets(),
$this->renderLegacyGroups(), $this->renderLegacyGroups(),
//$this->renderMultiRelations(), $this->renderLegacyMultiRelations(),
$this->renderLegacyRanges(),
$this->renderLegacyCustomExtensions(), $this->renderLegacyCustomExtensions(),
$this->renderLegacyCustomVars(), $this->renderLegacyCustomVars(),
$this->renderLegacySuffix() $this->renderLegacySuffix()
@ -2442,11 +2455,11 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
$this->renderPrioritizedProperties(), $this->renderPrioritizedProperties(),
$this->renderImports(), $this->renderImports(),
$this->renderProperties(), $this->renderProperties(),
$this->renderRanges(),
$this->renderArguments(), $this->renderArguments(),
$this->renderRelatedSets(), $this->renderRelatedSets(),
$this->renderGroups(), $this->renderGroups(),
$this->renderMultiRelations(), $this->renderMultiRelations(),
$this->renderRanges(),
$this->renderCustomExtensions(), $this->renderCustomExtensions(),
$this->renderCustomVars(), $this->renderCustomVars(),
$this->renderSuffix() $this->renderSuffix()

View File

@ -28,17 +28,28 @@ class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRen
protected $relationIdColumn; protected $relationIdColumn;
protected $relatedShortName;
protected $legacyPropertyName;
private $position = 0; private $position = 0;
private $db; private $db;
protected $idx = array(); protected $idx = array();
public function __construct(IcingaObject $object, $propertyName, $relatedObjectClass) public function __construct(IcingaObject $object, $propertyName, $config)
{ {
$this->object = $object; $this->object = $object;
$this->propertyName = $propertyName; $this->propertyName = $propertyName;
$this->relatedObjectClass = $relatedObjectClass;
if (is_object($config) || is_array($config)) {
foreach ($config as $k => $v) {
$this->$k = $v;
}
} else {
$this->relatedObjectClass = $config;
}
} }
public function getObjects() public function getObjects()
@ -238,15 +249,26 @@ class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRen
return $this->propertyName; return $this->propertyName;
} }
protected function getRelatedShortName()
{
if ($this->relatedShortName === null) {
/** @var IcingaObject $class */
$class = $this->getRelatedClassName();
$this->relatedShortName = $class::create()->getShortTableName();
}
return $this->relatedShortName;
}
protected function getTableName() protected function getTableName()
{ {
$class = $this->getRelatedClassName(); return $this->object->getTableName() . '_' . $this->getRelatedShortName();
return $this->object->getTableName() . '_' . $class::create()->getShortTableName();
} }
protected function getRelatedTableName() protected function getRelatedTableName()
{ {
if ($this->relatedTableName === null) { if ($this->relatedTableName === null) {
/** @var IcingaObject $class */
$class = $this->getRelatedClassName(); $class = $this->getRelatedClassName();
$this->relatedTableName = $class::create()->getTableName(); $this->relatedTableName = $class::create()->getTableName();
} }
@ -257,8 +279,7 @@ class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRen
protected function getRelationIdColumn() protected function getRelationIdColumn()
{ {
if ($this->relationIdColumn === null) { if ($this->relationIdColumn === null) {
$class = $this->getRelatedClassName(); $this->relationIdColumn = $this->getRelatedShortName();
$this->relationIdColumn = $class::create()->getShortTableName();
} }
return $this->relationIdColumn; return $this->relationIdColumn;
@ -420,6 +441,11 @@ class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRen
return ''; return '';
} }
return c1::renderKeyValue($this->propertyName, c1::renderArray($relations)); if ($this->legacyPropertyName === null) {
return ' # not supported in legacy: ' .
c1::renderKeyValue($this->propertyName, c1::renderArray($relations), '');
}
return c1::renderKeyValue($this->legacyPropertyName, c1::renderArray($relations));
} }
} }

View File

@ -14,10 +14,15 @@ class IcingaTimePeriod extends IcingaObject
'object_name' => null, 'object_name' => null,
'object_type' => null, 'object_type' => null,
'disabled' => 'n', 'disabled' => 'n',
'prefer_includes' => null,
'display_name' => null, 'display_name' => null,
'update_method' => null, 'update_method' => null,
); );
protected $booleans = [
'prefer_includes' => 'prefer_includes',
];
protected $supportsImports = true; protected $supportsImports = true;
protected $supportsRanges = true; protected $supportsRanges = true;
@ -28,6 +33,18 @@ class IcingaTimePeriod extends IcingaObject
'zone' => 'IcingaZone', 'zone' => 'IcingaZone',
); );
protected $multiRelations = [
'includes' => [
'relatedObjectClass' => 'IcingaTimeperiod',
'relatedShortName' => 'include',
],
'excludes' => [
'relatedObjectClass' => 'IcingaTimeperiod',
'relatedShortName' => 'exclude',
'legacyPropertyName' => 'exclude'
],
];
/** /**
* Render update property * Render update property
* *
@ -48,15 +65,64 @@ class IcingaTimePeriod extends IcingaObject
. ' import "legacy-timeperiod"' . "\n"; . ' import "legacy-timeperiod"' . "\n";
} }
protected function checkPeriodInRange($now, $name = null)
{
if ($name !== null) {
$period = static::load($name, $this->connection);
} else {
$period = $this;
}
foreach ($period->ranges()->getRanges() as $range) {
if ($range->isActive($now)) {
return true;
}
}
return false;
}
public function isActive($now = null) public function isActive($now = null)
{ {
if ($now === null) { if ($now === null) {
$now = time(); $now = time();
} }
foreach ($this->ranges()->getRanges() as $range) { $preferIncludes = $this->get('prefer_includes') !== 'n';
if ($range->isActive($now)) {
$active = $this->checkPeriodInRange($now);
$included = false;
$excluded = false;
$variants = [
'includes' => &$included,
'excludes' => &$excluded
];
foreach ($variants as $key => &$var) {
foreach ($this->get($key) as $name) {
if ($this->checkPeriodInRange($now, $name)) {
$var = true;
break;
}
}
}
if ($preferIncludes) {
if ($included) {
return true; return true;
} elseif ($excluded) {
return false;
} else {
return $active;
}
} else {
if ($excluded) {
return false;
} elseif ($included) {
return true;
} else {
return $active;
} }
} }

View File

@ -0,0 +1,38 @@
ALTER TABLE icinga_timeperiod
ADD COLUMN prefer_includes ENUM('y', 'n') DEFAULT NULL;
CREATE TABLE icinga_timeperiod_include (
timeperiod_id INT(10) UNSIGNED NOT NULL,
include_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (timeperiod_id, include_id),
CONSTRAINT icinga_timeperiod_include
FOREIGN KEY timeperiod (include_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT icinga_timeperiod_include_timeperiod
FOREIGN KEY include (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE icinga_timeperiod_exclude (
timeperiod_id INT(10) UNSIGNED NOT NULL,
exclude_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (timeperiod_id, exclude_id),
CONSTRAINT icinga_timeperiod_exclude
FOREIGN KEY timeperiod (exclude_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT icinga_timeperiod_exclude_timeperiod
FOREIGN KEY exclude (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (151, NOW());

View File

@ -197,6 +197,7 @@ CREATE TABLE icinga_timeperiod (
zone_id INT(10) UNSIGNED DEFAULT NULL, zone_id INT(10) UNSIGNED DEFAULT NULL,
object_type ENUM('object', 'template') NOT NULL, object_type ENUM('object', 'template') NOT NULL,
disabled ENUM('y', 'n') NOT NULL DEFAULT 'n', disabled ENUM('y', 'n') NOT NULL DEFAULT 'n',
prefer_includes ENUM('y', 'n') DEFAULT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE INDEX object_name (object_name, zone_id), UNIQUE INDEX object_name (object_name, zone_id),
CONSTRAINT icinga_timeperiod_zone CONSTRAINT icinga_timeperiod_zone
@ -1695,6 +1696,38 @@ CREATE TABLE icinga_dependency_states_set (
ON UPDATE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE icinga_timeperiod_include (
timeperiod_id INT(10) UNSIGNED NOT NULL,
include_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (timeperiod_id, include_id),
CONSTRAINT icinga_timeperiod_include
FOREIGN KEY timeperiod (include_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT icinga_timeperiod_include_timeperiod
FOREIGN KEY include (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE icinga_timeperiod_exclude (
timeperiod_id INT(10) UNSIGNED NOT NULL,
exclude_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (timeperiod_id, exclude_id),
CONSTRAINT icinga_timeperiod_exclude
FOREIGN KEY timeperiod (exclude_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE RESTRICT,
CONSTRAINT icinga_timeperiod_exclude_timeperiod
FOREIGN KEY exclude (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO director_schema_migration INSERT INTO director_schema_migration
(schema_version, migration_time) (schema_version, migration_time)
VALUES (150, NOW()); VALUES (151, NOW());

View File

@ -0,0 +1,38 @@
ALTER TABLE icinga_timeperiod
ADD COLUMN prefer_includes enum_boolean DEFAULT NULL;
CREATE TABLE icinga_timeperiod_include (
timeperiod_id integer NOT NULL,
include_id integer NOT NULL,
PRIMARY KEY (timeperiod_id, include_id),
CONSTRAINT icinga_timeperiod_timeperiod_include
FOREIGN KEY (include_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT icinga_timeperiod_include
FOREIGN KEY (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE icinga_timeperiod_exclude (
timeperiod_id integer NOT NULL,
exclude_id integer NOT NULL,
PRIMARY KEY (timeperiod_id, exclude_id),
CONSTRAINT icinga_timeperiod_timeperiod_exclude
FOREIGN KEY (exclude_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT icinga_timeperiod_exclude
FOREIGN KEY (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (151, NOW());

View File

@ -273,6 +273,7 @@ CREATE TABLE icinga_timeperiod (
zone_id integer DEFAULT NULL, zone_id integer DEFAULT NULL,
object_type enum_object_type_all NOT NULL, object_type enum_object_type_all NOT NULL,
disabled enum_boolean NOT NULL DEFAULT 'n', disabled enum_boolean NOT NULL DEFAULT 'n',
prefer_includes enum_boolean DEFAULT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT icinga_timeperiod_zone CONSTRAINT icinga_timeperiod_zone
FOREIGN KEY (zone_id) FOREIGN KEY (zone_id)
@ -1990,7 +1991,38 @@ CREATE TABLE icinga_dependency_states_set (
CREATE INDEX dependency_states_set_dependency ON icinga_dependency_states_set (dependency_id); CREATE INDEX dependency_states_set_dependency ON icinga_dependency_states_set (dependency_id);
COMMENT ON COLUMN icinga_dependency_states_set.merge_behaviour IS 'override: = [], extend: += [], blacklist: -= []'; COMMENT ON COLUMN icinga_dependency_states_set.merge_behaviour IS 'override: = [], extend: += [], blacklist: -= []';
CREATE TABLE icinga_timeperiod_include (
timeperiod_id integer NOT NULL,
include_id integer NOT NULL,
PRIMARY KEY (timeperiod_id, include_id),
CONSTRAINT icinga_timeperiod_timeperiod_include
FOREIGN KEY (include_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT icinga_timeperiod_include
FOREIGN KEY (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE icinga_timeperiod_exclude (
timeperiod_id integer NOT NULL,
exclude_id integer NOT NULL,
PRIMARY KEY (timeperiod_id, exclude_id),
CONSTRAINT icinga_timeperiod_timeperiod_exclude
FOREIGN KEY (exclude_id)
REFERENCES icinga_timeperiod (id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
CONSTRAINT icinga_timeperiod_exclude
FOREIGN KEY (timeperiod_id)
REFERENCES icinga_timeperiod (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO director_schema_migration INSERT INTO director_schema_migration
(schema_version, migration_time) (schema_version, migration_time)
VALUES (150, NOW()); VALUES (151, NOW());

View File

@ -9,6 +9,8 @@ class IcingaTimePeriodTest extends BaseTestCase
{ {
protected $testPeriodName = '___TEST___timeperiod'; protected $testPeriodName = '___TEST___timeperiod';
protected $createdNames = [];
public function testWhetherUpdatedTimeperiodsAreCorrectlyStored() public function testWhetherUpdatedTimeperiodsAreCorrectlyStored()
{ {
if ($this->skipForMissingDb()) { if ($this->skipForMissingDb()) {
@ -54,12 +56,15 @@ class IcingaTimePeriodTest extends BaseTestCase
); );
} }
protected function createTestPeriod() protected function createTestPeriod($suffix = '', $testRanges = [])
{ {
$db = $this->getDb(); $db = $this->getDb();
$name = $this->testPeriodName . $suffix;
$this->createdNames[] = $name;
$object = IcingaTimePeriod::create( $object = IcingaTimePeriod::create(
array( array(
'object_name' => $this->testPeriodName, 'object_name' => $name,
'object_type' => 'object' 'object_type' => 'object'
), ),
$db $db
@ -67,11 +72,13 @@ class IcingaTimePeriodTest extends BaseTestCase
$object->store(); $object->store();
$ranges = $object->ranges(); $ranges = $object->ranges();
$testRanges = array( if (empty($testRanges)) {
'monday' => '00:00-24:00', $testRanges = array(
'tuesday' => '00:00-24:00', 'monday' => '00:00-24:00',
'wednesday' => '00:00-24:00', 'tuesday' => '00:00-24:00',
); 'wednesday' => '00:00-24:00',
);
}
$ranges->set($testRanges); $ranges->set($testRanges);
$object->store(); $object->store();
@ -98,16 +105,80 @@ class IcingaTimePeriodTest extends BaseTestCase
$this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00'))); $this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00')));
} }
protected function loadTestPeriod() public function testPeriodWithIncludes()
{ {
return IcingaTimePeriod::load($this->testPeriodName, $this->getDb()); $period = $this->createTestPeriod();
$include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']);
$period->set('includes', $include->object_name);
$period->store();
// Wednesday:
$this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00')));
// Thursday:
$this->assertTrue($period->isActive(strtotime('2016-05-19 10:00:00')));
}
public function testPeriodWithExcludes()
{
$period = $this->createTestPeriod();
$exclude = $this->createTestPeriod('exclude', ['wednesday' => '00:00-24:00']);
$period->set('excludes', $exclude->object_name);
$period->store();
// Wednesday:
$this->assertFalse($period->isActive(strtotime('2016-05-18 10:00:00')));
// Thursday:
$this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00')));
}
public function testPeriodPreferingIncludes()
{
$period = $this->createTestPeriod();
$include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']);
$exclude = $this->createTestPeriod('exclude', ['thursday' => '00:00-24:00']);
$period->set('includes', $include->object_name);
$period->set('excludes', $exclude->object_name);
$period->store();
// Wednesday:
$this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00')));
// Thursday:
$this->assertTrue($period->isActive(strtotime('2016-05-19 10:00:00')));
}
public function testPeriodPreferingExcludes()
{
$period = $this->createTestPeriod();
$include = $this->createTestPeriod('include', ['thursday' => '00:00-24:00']);
$exclude = $this->createTestPeriod('exclude', ['thursday' => '00:00-24:00']);
$period->set('prefer_includes', false);
$period->set('includes', $include->object_name);
$period->set('excludes', $exclude->object_name);
$period->store();
// Wednesday:
$this->assertTrue($period->isActive(strtotime('2016-05-18 10:00:00')));
// Thursday:
$this->assertFalse($period->isActive(strtotime('2016-05-19 10:00:00')));
}
protected function loadTestPeriod($suffix = '')
{
return IcingaTimePeriod::load($this->testPeriodName . $suffix, $this->getDb());
} }
public function tearDown() public function tearDown()
{ {
$db = $this->getDb(); $db = $this->getDb();
if (IcingaTimePeriod::exists($this->testPeriodName, $db)) {
IcingaTimePeriod::load($this->testPeriodName, $db)->delete(); foreach ($this->createdNames as $name) {
if (IcingaTimePeriod::exists($name, $db)) {
IcingaTimePeriod::load($name, $db)->delete();
}
} }
} }
} }