From 2615446572be854cfb6ce4c318c1de1fde4d174e Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 13 Oct 2016 08:33:10 +0000 Subject: [PATCH] ScheduledDowntime: initial implementation refs #347 --- .../ScheduleddowntimesController.php | 9 + library/Director/Db.php | 1 + .../Objects/IcingaScheduledDowntime.php | 81 +++++ .../Objects/IcingaScheduledDowntimeRange.php | 89 ++++++ .../Objects/IcingaScheduledDowntimeRanges.php | 289 ++++++++++++++++++ 5 files changed, 469 insertions(+) create mode 100644 application/controllers/ScheduleddowntimesController.php create mode 100644 library/Director/Objects/IcingaScheduledDowntime.php create mode 100644 library/Director/Objects/IcingaScheduledDowntimeRange.php create mode 100644 library/Director/Objects/IcingaScheduledDowntimeRanges.php diff --git a/application/controllers/ScheduleddowntimesController.php b/application/controllers/ScheduleddowntimesController.php new file mode 100644 index 00000000..7ca3ca61 --- /dev/null +++ b/application/controllers/ScheduleddowntimesController.php @@ -0,0 +1,9 @@ + null, + 'zone_id' => null, + 'object_name' => null, + 'object_type' => null, + 'disabled' => 'n', + 'display_name' => null, + 'author' => null, + 'comment' => null, + 'fixed' => null, + 'duration' => null, + ); + + protected $supportsImports = true; + + protected $supportsRanges = true; + + protected $relations = array( + 'zone' => 'IcingaZone', + ); + + protected $booleans = array( + 'fixed' => 'fixed', + ); + + protected $intervalProperties = array( + 'duration' => 'duration', + ); + + /** + * Render update property + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderUpdate_method() + { + // @codingStandardsIgnoreEnd + return ''; + } + + protected function renderObjectHeader() + { + return parent::renderObjectHeader() + . ' import "legacy-timeperiod"' . "\n"; + } + + public function isActive($now = null) + { + if ($now === null) { + $now = time(); + } + + foreach ($this->ranges()->getRanges() as $range) { + if ($range->isActive($now)) { + return true; + } + } + + // TODO: no range currently means (and renders) "never", Icinga behaves + // different. Figure out whether and how we should support this + return false; + } + + protected function prefersGlobalZone() + { + return true; + } +} diff --git a/library/Director/Objects/IcingaScheduledDowntimeRange.php b/library/Director/Objects/IcingaScheduledDowntimeRange.php new file mode 100644 index 00000000..150b4335 --- /dev/null +++ b/library/Director/Objects/IcingaScheduledDowntimeRange.php @@ -0,0 +1,89 @@ + null, + 'range_key' => null, + 'range_value' => null, + 'range_type' => 'include', + 'merge_behaviour' => 'set', + ); + + public function isActive($now = null) + { + if ($now === null) { + $now = time(); + } + + if (false === ($wday = $this->getWeekDay($this->range_key))) { + // TODO, dates are not yet supported + return false; + } + + $wdayName = $this->range_key; + + if ((int) strftime('%w', $now) !== $wday) { + return false; + } + + $timeRanges = preg_split('/\s*,\s*/', $this->range_value, -1, PREG_SPLIT_NO_EMPTY); + foreach ($timeRanges as $timeRange) { + if ($this->timeRangeIsActive($timeRange, $now)) { + return true; + } + } + + return false; + } + + protected function timeRangeIsActive($rangeString, $now) + { + if (sscanf($rangeString, '%2d:%2d-%2d:%2d', $hBegin, $mBegin, $hEnd, $mEnd) === 4) { + if ($this->timeFromHourMin($hBegin, $mBegin, $now) <= $now + && $this->timeFromHourMin($hEnd, $mEnd, $now) >= $now + ) { + return true; + } + } else { + // TODO: throw exception? + } + + return false; + } + + protected function timeFromHourMin($hour, $min, $now) + { + return strtotime(sprintf('%s %02d:%02d:00', date('Y-m-d', $now), $hour, $min)); + } + + protected function getWeekDay($day) + { + switch ($day) { + case 'sunday': + return 0; + case 'monday': + return 1; + case 'tuesday': + return 2; + case 'wednesday': + return 3; + case 'thursday': + return 4; + case 'friday': + return 5; + case 'saturday': + return 6; + } + + return false; + } +} diff --git a/library/Director/Objects/IcingaScheduledDowntimeRanges.php b/library/Director/Objects/IcingaScheduledDowntimeRanges.php new file mode 100644 index 00000000..327c635c --- /dev/null +++ b/library/Director/Objects/IcingaScheduledDowntimeRanges.php @@ -0,0 +1,289 @@ +object = $object; + } + + public function count() + { + return count($this->ranges); + } + + public function rewind() + { + $this->position = 0; + } + + public function hasBeenModified() + { + return $this->modified; + } + + public function current() + { + if (! $this->valid()) { + return null; + } + + return $this->ranges[$this->idx[$this->position]]; + } + + public function key() + { + return $this->idx[$this->position]; + } + + public function next() + { + ++$this->position; + } + + public function valid() + { + return array_key_exists($this->position, $this->idx); + } + + public function get($key) + { + if (array_key_exists($key, $this->ranges)) { + return $this->ranges[$key]; + } + + return null; + } + + public function getValues() + { + $res = array(); + foreach ($this->ranges as $key => $range) { + $res[$key] = $range->range_value; + } + + return (object) $res; + } + + public function getOriginalValues() + { + $res = array(); + foreach ($this->storedRanges as $key => $range) { + $res[$key] = $range->range_value; + } + + return (object) $res; + } + + public function getRanges() + { + return $this->ranges; + } + + protected function modify($range, $value) + { + $this->ranges[$range]->range_key = $value; + } + + public function set($ranges) + { + foreach ($ranges as $range => $value) { + $this->setRange($range, $value); + } + + $toDelete = array_diff(array_keys($this->ranges), array_keys($ranges)); + foreach ($toDelete as $range) { + $this->remove($range); + } + + return $this; + } + + public function setRange($range, $value) + { + if ($value === null && array_key_exists($range, $this->ranges)) { + $this->remove($range); + return $this; + } + + if (array_key_exists($range, $this->ranges)) { + if ($this->ranges[$range]->range_value === $value) { + return $this; + } else { + $this->ranges[$range]->range_value = $value; + $this->modified = true; + } + } else { + $this->ranges[$range] = IcingaTimePeriodRange::create(array( + 'timeperiod_id' => $this->object->id, + 'range_key' => $range, + 'range_value' => $value, + )); + $this->modified = true; + } + + return $this; + } + + /** + * Magic isset check + * + * @return boolean + */ + public function __isset($range) + { + return array_key_exists($range, $this->ranges); + } + + public function remove($range) + { + if (array_key_exists($range, $this->ranges)) { + unset($this->ranges[$range]); + } + + $this->modified = true; + $this->refreshIndex(); + } + + public function clear() + { + $this->ranges = array(); + $this->modified = true; + $this->refreshIndex(); + } + + protected function refreshIndex() + { + ksort($this->ranges); + $this->idx = array_keys($this->ranges); + } + + protected function getRangeClass() + { + return __NAMESPACE__ . '\\Icinga' .ucfirst($this->object->getShortTableName()) . 'Range'; + } + + public function listRangesNames() + { + return array_keys($this->ranges); + } + + public function getType() + { + return $this->object->getShortTableName(); + } + + public function getRangeTableName() + { + return $this->object->getTableName() . '_range'; + } + + protected function loadFromDb() + { + $db = $this->object->getDb(); + $connection = $this->object->getConnection(); + + $table = $this->getRangeTableName(); + + $query = $db->select()->from( + array('o' => $table) + )->where('o.timeperiod_id = ?', (int) $this->object->id) + ->order('o.range_key'); + + $class = $this->getClass(); + $this->ranges = $class::loadAll($connection, $query, 'range_key'); + $this->storedRanges = array(); + + foreach ($this->ranges as $key => $range) { + $this->storedRanges[$key] = clone($range); + } + + return $this; + } + + public function store() + { + foreach ($this->ranges as $range) { + $range->timeperiod_id = $this->object->id; + $range->store($this->object->getConnection()); + } + + foreach (array_diff(array_keys($this->storedRanges), array_keys($this->ranges)) as $delete) { + $this->storedRanges[$delete]->delete(); + } + + $this->storedRanges = $this->ranges; + + return true; + } + + protected function getClass() + { + return __NAMESPACE__ . '\\IcingaTimePeriodRange'; + } + + public static function loadForStoredObject(IcingaObject $object) + { + $ranges = new static($object); + return $ranges->loadFromDb(); + } + + public function toConfigString() + { + if (empty($this->ranges) && $this->object->object_type === 'template') { + return ''; + } + + $string = " ranges = {\n"; + + foreach ($this->ranges as $range) { + $string .= sprintf( + " %s\t= %s\n", + c::renderString($range->range_key), + c::renderString($range->range_value) + ); + } + + return $string . " }\n"; + } + + public function __toString() + { + try { + return $this->toConfigString(); + } catch (Exception $e) { + trigger_error($e); + $previousHandler = set_exception_handler( + function () { + } + ); + restore_error_handler(); + if ($previousHandler !== null) { + call_user_func($previousHandler, $e); + die(); + } else { + die($e->getMessage()); + } + } + } +}