diff --git a/library/Icinga/Application/Logger.php b/library/Icinga/Application/Logger.php new file mode 100755 index 000000000..bf3c01a16 --- /dev/null +++ b/library/Icinga/Application/Logger.php @@ -0,0 +1,152 @@ +overwrite($config); + } + + public function getWriters() { + return $this->writers; + } + + public function overwrite(\Zend_Config $config) + { + $this->clearLog(); + try { + if ($config->debug && $config->debug->enable == 1) + $this->setupDebugLog($config); + } catch (\Icinga\Exception\ConfigurationError $e) { + $this->warn("Could not create debug log: {$e->getMessage()}"); + } + + $this->setupLog($config); + $this->flushQueue(); + } + + private function setupDebugLog(\Zend_Config $config) + { + $type = $config->debug->get("type", self::DEFAULT_LOG_TYPE); + $target = $config->debug->get("target", self::DEFAULT_LOG_TARGET); + if ($target == self::DEFAULT_LOG_TARGET) + $type == self::DEFAULT_LOG_TYPE; + + $this->addWriter($type, $target, \Zend_Log::DEBUG); + } + + private function setupLog(\Zend_Config $config) + { + $type = $config->get("type", self::DEFAULT_LOG_TYPE); + $target = $config->get("target", self::DEFAULT_DEBUG_TARGET); + if ($target == self::DEFAULT_DEBUG_TARGET) + $type == self::DEFAULT_LOG_TYPE; + $level = \Zend_Log::WARN; + if ($config->get("verbose", 0) == 1) + $level = \Zend_Log::INFO; + $this->addWriter($type, $target, $level); + } + + private function addWriter($type, $target , $priority) + { + $type[0] = strtoupper($type[0]); + $writerClass = "\Zend_Log_Writer_" . $type; + if (!class_exists($writerClass)) + throw new \Icinga\Exception\ConfigurationError("Could not create log: Unknown type " . $type); + + $writer = new $writerClass($target); + + $writer->addFilter(new \Zend_Log_Filter_Priority($priority)); + $this->logger->addWriter($writer); + $this->writers[] = $writer; + } + + public function flushQueue() + { + foreach(self::$queue as $msgTypePair) { + $this->logger->log($msgTypePair[0],$msgTypePair[1]); + } + } + + public static function formatMessage(array $argv) + { + + if (count($argv) == 1) { + $format = $argv[0]; + } else { + $format = array_shift($argv); + } + if (!is_string($format)) { + $format = json_encode($format); + } + foreach ($argv as &$arg) { + if (!is_string($arg)) + $arg = json_encode($arg); + } + + return @vsprintf($format, $argv); + } + + + public function clearLog() + { + $this->logger = null; + $this->writers = array(); + $this->logger = new \Zend_Log(); + } + + public static function create(\Zend_Config $config) + { + if (self::$instance) + return self::$instance->overwrite($config); + return self::$instance = new Logger($config); + } + + public static function debug() + { + self::log(self::formatMessage(func_get_args()),\Zend_Log::DEBUG); + } + + public static function warn() { + self::log(self::formatMessage(func_get_args()),\Zend_Log::WARN); + } + + public static function info() { + self::log(self::formatMessage(func_get_args()),\Zend_Log::INFO); + } + + public static function error() { + self::log(self::formatMessage(func_get_args()),\Zend_Log::ERR); + } + + public static function fatal() { + self::log(self::formatMessage(func_get_args()),\Zend_Log::EMERG); + } + + private static function log($msg,$level = \Zend_Log::INFO) { + $logger = self::$instance; + + if(!$logger) { + array_push(self::$queue, array($msg,$level)); + return; + } + + $logger->logger->log($msg,$level); + } + + + public static function reset() { + self::$queue = array(); + self::$instance = null; + } +} diff --git a/library/Icinga/Exception/ConfigurationError.php b/library/Icinga/Exception/ConfigurationError.php new file mode 100644 index 000000000..b6df04795 --- /dev/null +++ b/library/Icinga/Exception/ConfigurationError.php @@ -0,0 +1,8 @@ +filehandle = $filehandle; + $this->icingaState = $baseState; + } + + public function parseObjectsFile() + { + \Icinga\Application\Logger::debug("Reading new objects file"); + $DEFINE = strlen("define "); + $filehandle = $this->filehandle; + $this->icingaState = array(); + while (!feof($filehandle)) { + + $line = trim(fgets($filehandle)); + + $this->lineCtr++; + if ($line === "" || $line[0] === "#") + continue; + $this->currentObjectType = trim(substr($line,$DEFINE,-1)); + if (!isset($this->icingaState[$this->currentObjectType])) { + $this->icingaState[$this->currentObjectType] = array(); + } + $this->readCurrentObject(); + } + $this->processDeferred(); + } + + public function parseRuntimeState($filehandle = null) + { + if($filehandle != null) + $this->filehandle = $filehandle; + else + $filehandle = $this->filehandle; + + if(!$this->icingaState) + throw new \Icinga\Exception\ProgrammingError("Tried to read runtime state without existing objects data"); + $this->overwrites = array(); + while (!feof($filehandle)) { + + $line = trim(fgets($filehandle)); + + $this->lineCtr++; + if ($line === "" || $line[0] === "#") + continue; + + $this->currentStateType = trim(substr($line,0,-1)); + $this->readCurrentState(); + } + + } + + private function readCurrentObject() + { + $filehandle = $this->filehandle; + $monitoringObject = new \stdClass(); + while (!feof($filehandle)) { + $line = explode("\t",trim(fgets($filehandle)),2); + $this->lineCtr++; + if (!$line) + continue; + + // End of object + if ($line[0] === "}") { + $this->registerObject($monitoringObject); + return; + } + if(!isset($line[1])) + $line[1] = ""; + $monitoringObject->{$line[0]} = trim($line[1]); + } + throw new ParsingException("Unexpected EOF in objects.cache, line ".$this->lineCtr); + } + + /** + * TODO: Discard old runtime data + * @throws Exception\ParsingException + */ + private function readCurrentState() + { + $filehandle = $this->filehandle; + $statusdatObject = new RuntimeStateContainer(); + + $objectType = $this->getObjectTypeForState(); + + if($objectType != "host" && $objectType != "service") { + $this->skipObject(); // ignore unknown objects + return; + } + if(!isset($this->icingaState[$this->currentObjectType])) + throw new ParsingException("No $this->currentObjectType objects registered in objects.cache"); + $base = &$this->icingaState[$this->currentObjectType]; + $state = &$this->skipObject(true); + $statusdatObject->runtimeState = &$state; + $name = $this->getObjectIdentifier($statusdatObject); + + if(!isset($base[$name])) + throw new ParsingException("Unknown object $name ".$this->currentObjectType." - ".print_r($statusdatObject,true)."\n".print_r($base,true)); + $type = substr($this->currentStateType,strlen($objectType)); + + if($type == "status") { + $base[$name]->status = &$statusdatObject; + } else { + if(!isset($base[$name]->$type) || !in_array($base[$name]->$type,$this->overwrites)) { + $base[$name]->$type = array(); + $this->overwrites[] = &$base[$name]->$type; + } + array_push($base[$name]->$type,$statusdatObject); + } + return; + + } + + private function getObjectTypeForState() + { + $pos = strpos($this->currentStateType,"service"); + + if($pos === False) { + $pos = strpos($this->currentStateType,"host"); + } else { + $this->currentObjectType = "service"; + return "service"; + } + + if($pos === False) + return $this->currentStateType; + else { + $this->currentObjectType = "host"; + return "host"; + } + + return $this->currentObjectType; + } + + protected function skipObject($returnString = false) + { + if(!$returnString) { + while(trim(fgets($this->filehandle)) !== "}") { + } + return; + } else { + $str = ""; + while(($val = trim(fgets($this->filehandle))) !== "}") { + $str .= $val."\n"; + } + return $str; + } + } + + protected function registerObject(&$object) { + + $name = $this->getObjectIdentifier($object); + + if($name !== false) { + $this->icingaState[$this->currentObjectType][$name] = &$object; + } + $this->registerObjectAsProperty($object); + } + + protected function registerObjectAsProperty(&$object) + { + if($this->currentObjectType == "service" || $this->currentObjectType == "host") { + return; + } + $isService = strpos($this->currentObjectType,"service") !== False; + $isHost = strpos($this->currentObjectType,"host") !== False; + + $name = $this->getObjectIdentifier($object); + if($isService === false && $isHost === false) // this would be error in the parser implementation + return; + $property = $this->currentObjectType; + if($isService) { + $this->currentObjectType = "service"; + $property = substr($property,strlen("service")); + } else { + $this->currentObjectType = "host"; + $property = substr($property,strlen("host")); + } + if(!isset($this->icingaState[$this->currentObjectType])) + return $this->deferRegistration($object,$this->currentObjectType.$property); + + // @TODO: Clean up, this differates between 1:n and 1:1 references + if(strpos($property ,"group") !== False) { + $sourceIdentifier = $this->getMembers($object); + foreach($sourceIdentifier as $id) { + $source = $this->icingaState[$this->currentObjectType][$id]; + if(!isset($source->$property)) + $source->$property = array(); + array_push($source->$property, $name); + } + } else { + $source = $this->icingaState[$this->currentObjectType][$this->getObjectIdentifier($object)]; + if(!isset($source->$property)) + $source->$property = array(); + array_push($source->$property, $object); + } + } + + protected function deferRegistration($object,$objType) + { + $this->deferred[] = array($object,$objType); + } + + protected function processDeferred() { + foreach($this->deferred as $obj) { + $this->currentObjectType = $obj[1]; + $this->registerObjectAsProperty($obj[0]); + } + } + + protected function getMembers(&$object) + { + $members = explode(",",$object->members); + if($this->currentObjectType == "service") { + $res = array(); + for($i=0;$icurrentObjectType == "service") { + return $object->host_name.";".$object->service_description; + } + $name = $this->currentObjectType."_name"; + if(isset($object->{$name})) + return $object->{$name}; + return false; + + + } + + + + public function getRuntimeState() + { + return $this->icingaState; + } + +} \ No newline at end of file diff --git a/library/Icinga/Protocol/Statusdat/Query.php b/library/Icinga/Protocol/Statusdat/Query.php new file mode 100755 index 000000000..8ee011fff --- /dev/null +++ b/library/Icinga/Protocol/Statusdat/Query.php @@ -0,0 +1,256 @@ + array("host"), + "services" => array("service"), + "downtimes" => array("hostdowntime", "servicedowntime"), + "hostdowntimes" => array("hostdowntime"), + "servicedowntimes" => array("servicedowntime"), + "hostgroups" => array("hostgroup"), + "servicegroups" => array("servicegroup"), + "comments" => array("servicecomment", "hostcomment"), + "hostcomments" => array("hostcomment"), + "servicecomments" => array("servicecomment") + ); + + private $reader = null; + private $source = ""; + private $columns = array(); + private $limit = null; + private $offset = 0; + private $order_columns = array(); + private $groupColumns = array(); + private $groupByFn = null; + private $filter = array(); + + // Magic indexes + const FN_SCOPE = 0; + const FN_NAME = 1; + + public function hasOrder() + { + return !empty($this->order_columns); + } + + public function hasColumns() + { + return !empty($this->columns); + } + + public function getColumns() + { + return $this->columns; + + } + + public function hasLimit() + { + return $this->limit !== false; + } + + public function hasOffset() + { + return $this->offset !== false; + } + + public function getLimit() + { + return $this->limit; + } + + public function getOffset() + { + return $this->offset; + } + + public function __construct(IReader $reader) + { + $this->reader = $reader; + } + + public function where($key, $val = null) + { + $this->filter[] = array($key, $val); + return $this; + } + + public function order($columns, $dir = null) + { + if($dir && strtolower($dir) == "desc") + $dir = self::SORT_DESC; + else + $dir = self::SORT_ASC; + if(!is_array($columns)) + $columns = array($columns); + foreach($columns as $col) { + + if (($pos = strpos($col, ' ')) !== false) { + $dir = strtoupper(substr($col, $pos + 1)); + if ($dir === 'DESC') { + $dir = self::SORT_DESC; + } else { + $dir = self::SORT_ASC; + } + $col = substr($col, 0, $pos); + } else { + $col = $col; + } + + $this->order_columns[] = array($col, $dir); + } + return $this; + } + + public function limit($count = null, $offset = 0) + { + if ((is_null($count) || is_integer($count)) && (is_null($offset) || is_integer($offset))) { + $this->offset = $offset; + $this->limit = $count; + } else { + throw new Exception("Got invalid limit $count, $offset"); + } + return $this; + } + + public function from($table, $columns = null) + { + if (isset(self::$VALID_TARGETS[$table])) + $this->source = $table; + else + throw new \Exception("Unknown from target for status.dat :". $table); + $this->columns = $columns; + return $this; + } + + /** + * + * @throws Exception + */ + private function getFilteredIndices($classType = "\Icinga\Protocol\Statusdat\Query\Group") + { + $baseGroup = null; + if (!empty($this->filter)) { + $baseGroup = new $classType(); + + foreach ($this->filter as $values) { + $baseGroup->addItem(new $classType($values[0], $values[1])); + } + } + + $state = $this->reader->getObjects(); + $result = array(); + foreach (self::$VALID_TARGETS[$this->source] as $target) { + $indexes = & array_keys($state[$target]); + if ($baseGroup) { + $indexes = & $baseGroup->filter($state[$target]); + } + if (!isset($result[$target])) { + $result[$target] = $indexes; + } else { + array_merge($result[$target], $indexes); + } + } + return $result; + } + + private function orderIndices(array &$indices) { + if(!empty($this->order_columns)) { + foreach($indices as $type=>&$subindices) { + $this->currentType = $type; // we're singlethreaded, so let's do it a bit dirty + usort($subindices,array($this,"orderResult")); + } + } + } + + private function orderResult($a,$b) { + $o1 = &$this->reader->getObjectByName($this->currentType,$a); + $o2 = &$this->reader->getObjectByName($this->currentType,$b); + $result = 0; + foreach($this->order_columns as $col) { + + $result += $col[1]*strnatcasecmp($o1->{$col[0]},$o2->{$col[0]}); + } + if($result > 0) + return 1; + if($result < 0) + return -1; + return 0; + } + + private function limitIndices(array &$indices) + { + foreach($indices as $type=>$subindices){ + $indices[$type] = array_slice($subindices,$this->offset,$this->limit); + } + } + + public function groupByFunction($fn,$scope=null) + { + $this->groupByFn = array($scope ? $scope : $this,$fn); + return $this; + } + + public function groupByColumns($columns) + { + if(!is_array($columns)) + $columns = array($columns); + $this->groupColumns = $columns; + $this->groupByFn = array($this,"columnGroupFn"); + return $this; + } + + private function columnGroupFn(array &$indices) + { + $cols = $this->groupColumns; + $result = array(); + foreach($indices as $type=>$subindices) { + foreach($subindices as $objectIndex) { + $r = &$this->reader->getObjectByName($type,$objectIndex); + $hash = ""; + $cols = array(); + foreach($this->groupColumns as $col) { + $hash = md5($hash.$r->$col); + $cols[$col] = $r->$col; + } + if(!isset($result[$hash])) + $result[$hash] = (object) array( + "columns" => (object) $cols, + "count" => 0 + ); + $result[$hash]->count++; + } + } + return array_values($result); + } + + public function getResult() + { + + $indices = &$this->getFilteredIndices(); + $this->orderIndices($indices); + if($this->groupByFn) { + $scope = $this->groupByFn[self::FN_SCOPE]; + $fn = $this->groupByFn[self::FN_NAME]; + + return $scope->$fn($indices); + } + + $this->limitIndices($indices); + + $result = array(); + $state = &$this->reader->getObjects(); + foreach ($indices as $type=>$subindices) { + + foreach($subindices as $index) { + $result[] = &$state[$type][$index]; + } + } + return $result; + } + +} diff --git a/library/Icinga/Protocol/Statusdat/Query/Expression.php b/library/Icinga/Protocol/Statusdat/Query/Expression.php new file mode 100755 index 000000000..7d496d2e0 --- /dev/null +++ b/library/Icinga/Protocol/Statusdat/Query/Expression.php @@ -0,0 +1,216 @@ +": + $this->CB = "IS_GREATER"; + break; + case "<": + $this->CB = "IS_LESS"; + break; + case ">=": + $this->CB = "IS_GREATER_EQ"; + break; + case "<=": + $this->CB = "IS_LESS_EQ"; + break; + case "=": + $this->CB = "IS_EQUAL"; + break; + case "LIKE": + $this->CB = "IS_LIKE"; + break; + case "!=": + $this->CB = "IS_NOT_EQUAL"; + break; + case "IN": + $this->CB = "IS_IN"; + break; + + default: + throw new \Exception("Unknown operator $token in expression $this->expression !"); + } + } + + + private function extractAggregationFunction(&$tokens) { + $token = $tokens[0]; + $value = array(); + if(preg_match("/COUNT\{(.*)\}/",$token,$value) == false) + return $token; + $this->function = "count"; + $tokens[0] = $value[1]; + } + + private function parseExpression(&$values) + { + $tokenized = preg_split("/ +/", trim($this->expression), 3); + $this->extractAggregationFunction($tokenized); + if (count($tokenized) != 3) + echo ("Currently statusdat query expressions must be in the format FIELD OPERATOR ? or FIELD OPERATOR :value_name"); + + $this->fields = explode(".",trim($tokenized[0])); + $this->field = $this->fields[count($this->fields)-1]; + $this->getOperatorType(trim($tokenized[1])); + $tokenized[2] = trim($tokenized[2]); + + if ($tokenized[2][0] === ":") { + $this->name = substr($tokenized, 1); + $this->value = $values[$this->name]; + } else if ($tokenized[2] === "?") { + $this->value = array_shift($values); + } else { + $this->value = trim($tokenized[2]); + } + + } + + public function fromString($expression, &$values) + { + $this->expression = $expression; + $this->parseExpression($values); + return $this; + } + + public function __construct($expression = null, &$values = array()) + { + if ($expression) + $this->fromString($expression, $values); + + } + + public function filter(array &$base, &$idx = array()) + { + if (!$idx) + $idx = array_keys($base); + $this->basedata = $base; + return array_filter($idx, array($this,"filterFn")); + } + + public function getValue() + { + return $this->value; + } + + public function getField() + { + return $this->field; + } + + protected function filterFn($idx) { + $values = $this->getFieldValues($idx); + + if($values === False) + return false; + + if($this->CB == "IS_IN" ) { + return count(array_intersect($values,$this->value)) > 0; + } + if($this->CB == "IS_NOT_IN" ) + return count(array_intersect($values,$this->value)) == 0; + if($this->function) { + $values = call_user_func($this->function,$values); + if(!is_array($values)) + $values = array($values); + } + foreach($values as $val) + if($this->{$this->CB}($val)) + return true; + + return false; + } + + + + private function getFieldValues($idx) { + $res = $this->basedata[$idx]; + foreach($this->fields as $field) { + if(!is_array($res)) { + if(!isset($res->$field)) { + $res = array(); + break; + } + $res = $res->$field; + continue; + } + + // it can be that an element contains more than one value, like it + // happens when using comments, in this case we have to create a new + // array that contains the values/objects we're searching + $swap = array(); + foreach($res as $sub) { + if(!isset($sub->$field)) + continue; + if(!is_array($sub->$field)) + $swap[] = $sub->$field; + else { + $swap = array_merge($swap,$sub->$field); + } + } + $res = $swap; + } + if(!is_array($res)) + return array($res); + return $res; + } + + public function IS_GREATER($value) + { + return $value > $this->value; + } + + public function IS_LESS($value) + { + return $value < $this->value; + } + + public function IS_LIKE($value) + { + + return preg_match("/^".str_replace("%", ".*", $this->value)."$/", $value) ? true : false; + } + + public function IS_EQUAL($value) + { + if(!is_numeric($value)) + return strtolower($value) ==strtolower($this->value); + return $value == $this->value; + } + + public function IS_NOT_EQUAL($value) + { + return $value != $this->value; + } + + public function IS_GREATER_EQ($value) + { + return $value >= $this->value; + } + + public function IS_LESS_EQ($value) + { + return $value <= $this->value; + } + + + +} \ No newline at end of file diff --git a/library/Icinga/Protocol/Statusdat/Query/Group.php b/library/Icinga/Protocol/Statusdat/Query/Group.php new file mode 100755 index 000000000..e3448ea29 --- /dev/null +++ b/library/Icinga/Protocol/Statusdat/Query/Group.php @@ -0,0 +1,226 @@ +value = $value; + } + + public function getItems() + { + return $this->items; + } + + public function getType() + { + return $this->type ? $this->type : self::TYPE_AND; + } + + public function setType($type) + { + $this->type = $type; + } + + private function tokenize() + { + $token = 0; + $subgroupCount = 0; + while ($token != self::EOF) { + + $token = $this->getNextToken(); + + if ($token === self::GROUP_BEGIN) { + + if ($subgroupCount == 0) // check if this is a nested group, if so then it's considered part of the subexpression + $this->startNewSubExpression(); + $subgroupCount++; + continue; + } + if ($token === self::GROUP_END) { + if ($subgroupCount < 1) + throw new \Exception("Invalid Query: unexpected ')' at pos " . $this->parsePos); + $subgroupCount--; + if ($subgroupCount == 0) // check if this is a nested group, if so then it's considered part of the subexpression + $this->addSubgroupFromExpression(); + continue; + } + + if ($token === self::CONJUNCTION_AND && $subgroupCount == 0) { + $this->startNewSubExpression(); + if ($this->type != self::TYPE_AND && $this->type != "") { + $this->createImplicitGroup(self::TYPE_AND); + break; + } else { + $this->type = self::TYPE_AND; + } + continue; + } + if ($token === self::CONJUNCTION_OR && $subgroupCount == 0) { + $this->startNewSubExpression(); + if ($this->type != self::TYPE_OR && $this->type != "") { + $this->createImplicitGroup(self::TYPE_OR); + break; + } else { + + $this->type = self::TYPE_OR; + } + continue; + } + + $this->subExpressionLength = $this->parsePos - $this->subExpressionStart; + } + if ($subgroupCount > 0) + throw new \Exception("Unexpected end of query, are you missing a parenthesis?"); + + $this->startNewSubExpression(); + } + + private function createImplicitGroup($type) + { + $group = new Group(); + $group->setType($type); + $group->addItem(array_pop($this->items)); + + $group->fromString(substr($this->expression, $this->parsePos), $this->value, $this->expressionClass); + $this->items[] = $group; + $this->parsePos = strlen($this->expression); + + } + + private function startNewSubExpression() + { + if ($this->getCurrentSubExpression() != "") { + if (!$this->expressionClass) + $this->items[] = new Expression($this->getCurrentSubExpression(), $this->value); + else + $this->items[] = new $this->expressionClass($this->getCurrentSubExpression(), $this->value); + } + + $this->subExpressionStart = $this->parsePos; + $this->subExpressionLength = 0; + } + + private function getCurrentSubExpression() + { + + return substr($this->expression, $this->subExpressionStart, $this->subExpressionLength); + } + + private function addSubgroupFromExpression() + { + + if (!$this->expressionClass) { + $this->items[] = new Group($this->getCurrentSubExpression(), $this->value); + } else { + $group = new Group(); + $group->fromString($this->getCurrentSubExpression(), $this->value, $this->expressionClass); + $this->items[] = $group; + } + $this->subExpressionStart = $this->parsePos; + $this->subExpressionLength = 0; + } + + private function isEOF() + { + if ($this->parsePos >= strlen($this->expression)) + return true; + return false; + } + + private function getNextToken() + { + if ($this->isEOF()) + return self::EOF; + + // skip whitespaces + while ($this->expression[$this->parsePos] == " ") { + $this->parsePos++; + if ($this->isEOF()) + return self::EOF; + } + if ($this->expression[$this->parsePos] == self::GROUP_BEGIN) { + $this->parsePos++; + return self::GROUP_BEGIN; + } + if ($this->expression[$this->parsePos] == self::GROUP_END) { + $this->parsePos++; + return self::GROUP_END; + } + if (substr_compare($this->expression, self::CONJUNCTION_AND, $this->parsePos, strlen(self::CONJUNCTION_AND), true) === 0) { + $this->parsePos += strlen(self::CONJUNCTION_AND); + return self::CONJUNCTION_AND; + } + if (substr_compare($this->expression, self::CONJUNCTION_OR, $this->parsePos, strlen(self::CONJUNCTION_OR), true) === 0) { + $this->parsePos += strlen(self::CONJUNCTION_OR); + return self::CONJUNCTION_OR; + } + $this->parsePos++; + return self::EXPRESSION; + } + + public function addItem($ex) + { + $this->items[] = $ex; + return $this; + } + + public function fromString($expression, &$value = array(), $expressionClass = null) + { + $this->expression = $expression; + $this->value = & $value; + $this->expressionClass = $expressionClass; + + $this->tokenize(); + return $this; + } + + + public function __construct($expression = null, &$value = array()) + { + if ($expression) + $this->fromString($expression, $value); + } + + public function filter(array &$base, &$idx = null) + { + + if ($this->type == self::TYPE_OR) { + $idx = array(); + foreach ($this->items as &$subFilter) { + $idx += $subFilter->filter($base, array_keys($base)); + } + } else { + if (!$idx) + $idx = array_keys($base); + foreach ($this->items as $subFilter) { + $idx = array_intersect($idx, $subFilter->filter($base, $idx)); + } + } + + return $idx; + } +} + diff --git a/library/Icinga/Protocol/Statusdat/Query/IQueryPart.php b/library/Icinga/Protocol/Statusdat/Query/IQueryPart.php new file mode 100755 index 000000000..2849eb745 --- /dev/null +++ b/library/Icinga/Protocol/Statusdat/Query/IQueryPart.php @@ -0,0 +1,10 @@ +ref = &$obj; + $this->reader = &$reader; + + } + public function __get($attribute) { + $exploded = explode(".",$attribute); + $result = $this->ref; + foreach($exploded as $elem) { + + $result = $result->$elem; + } + return $result; + } +} + +class Reader implements IReader +{ + const DEFAULT_CACHE_LIFETIME = 300; + const STATUSDAT_DEFAULT_CACHE_PATH = "/cache"; + + + private $lastState = null; + private $hasRuntimeState = false; + private $objectCache = null; + private $statusCache = null; + private $newState = false; + private $parser = null; + private $noCache = false; + public function __construct($config = \Zend_Config, $parser = null, $noCache = false) + { + $this->noCache = $noCache; + $this->config = $config; + $this->parser = $parser; + if(!$noCache) { + $this->cache = $this->initializeCaches($config); + if($this->fromCache()) { + $this->createHostServiceConnections(); + return; + } + } + if(!$this->lastState) + $this->parseObjectsCacheFile(); + if(!$this->hasRuntimeState); + $this->parseStatusDatFile(); + if(!$noCache && $this->newState) + $this->statusCache->save($this->parser->getRuntimeState(),'objects'.md5($this->config->objects_file)); + $this->createHostServiceConnections(); + + } + + private function createHostServiceConnections() + { + if (!isset($this->lastState["service"])) { + return; + } + + foreach ($this->lastState["service"] as &$service) { + $host = &$this->lastState["host"][$service->host_name]; + if(!isset($host->services)) + $host->services = array(); + $host->services[$service->service_description] = &$service; + $service->host = &$host; + } + } + + public function select() + { + return new Query($this); + } + + public function fetchAll(Query $query) + { + return new \Icinga\Backend\MonitoringObjectList( + $query->getResult(), + $query->getView() + ); + } + + public function getState() + { + return $this->lastState; + } + + public function getObjects() + { + return $this->lastState; + } + + + public function getObjectByName($type, $name) + { + if (isset($this->lastState[$type]) && isset($this->lastState[$type][$name])) + return new ObjectContainer($this->lastState[$type][$name],$this); + return null; + } + + public function getObjectNames($type) { + return isset($this->lastState[$type]) ? array_keys($this->lastState[$type]) : null; + } + + private function fromCache() + { + if(!$this->readObjectsCache()) { + $this->newState = true; + return false; + } + if(!$this->readStatusCache()){ + $this->newState = true; + return false; + } + + + return true; + } + + private function readObjectsCache() + { + $this->lastState = $this->objectCache->load('objects'.md5($this->config->objects_file)); + if($this->lastState == false) + return false; + } + + private function readStatusCache() + { + $statusInfo = $this->stateCache->load('state'.md5($this->config->status_file)); + if($statusInfo == false) + return false; + $this->hasRuntimeState = true; + } + + private function initializeCaches() + { + $defaultCachePath = "/tmp/".self::STATUSDAT_DEFAULT_CACHE_PATH; + + $cachePath = $this->config->get('cache_path',$defaultCachePath); + $maxCacheLifetime = intval($this->config->get('cache_path',self::DEFAULT_CACHE_LIFETIME)); + if(!is_writeable($cachePath)) + throw new \Icinga\Exception\ConfigurationError("Cache path $cachePath is not writable, check your configuration"); + + + $backendOptions = array( + 'cache_dir' => $cachePath + ); + // the objects cache might exist for months and is still valid + $this->objectCache = $this->initCache($this->config->objects_file,$backendOptions,NULL); + $this->statusCache = $this->initCache($this->config->status_file,$backendOptions,$maxCacheLifetime); + + } + + private function initCache($file, $backend, $lifetime) + { + $frontendOptions = array( + 'lifetime' => $lifetime, + 'automatic_serialization' => true, + 'master_files' => array($file) + ); + return \Zend_Cache::factory('Core','File',$frontendOptions,$backend); + } + + private function parseObjectsCacheFile() + { + if(!is_readable($this->config->objects_file)) + throw new \Icinga\Exception\ConfigurationError("Can't read objects-file {$this->config->objects_file}, check your configuration"); + if(!$this->parser) + $this->parser = new Parser(fopen($this->config->objects_file,"r")); + $this->parser->parseObjectsFile(); + $this->lastState = &$this->parser->getRuntimeState(); + } + + private function parseStatusDatFile() + { + if(!is_readable($this->config->status_file)) + throw new \Icinga\Exception\ConfigurationError("Can't read status-file {$this->config->status_file}, check your configuration"); + if(!$this->parser) + $this->parser = new Parser(fopen($this->config->status_file,"r"),$this->lastState); + $this->parser->parseRuntimeState(fopen($this->config->status_file,"r")); + $this->lastState = &$this->parser->getRuntimeState(); + if(!$this->noCache) + $this->statusCache->save(array("true" => true),"state".md5($this->config->objects_file)); + } + + +} diff --git a/library/Icinga/Protocol/Statusdat/RuntimeStateContainer.php b/library/Icinga/Protocol/Statusdat/RuntimeStateContainer.php new file mode 100755 index 000000000..ae81ffcae --- /dev/null +++ b/library/Icinga/Protocol/Statusdat/RuntimeStateContainer.php @@ -0,0 +1,35 @@ +runtimeState = $str; + } + + public function __isset($attr) + { + try { + $this->__get($attr); + return true; + } catch(\InvalidArgumentException $e) { + return false; + } + } + + public function __get($attr) + { + + $start = strpos($this->runtimeState,$attr."="); + if($start === False) + throw new \InvalidArgumentException("Unknown property $attr"); + + $start += strlen($attr."="); + $len = strpos($this->runtimeState,"\n",$start) - $start; + $this->$attr = trim(substr($this->runtimeState,$start,$len)); + + return $this->$attr; + } +} diff --git a/tests/php/library/Icinga/Protocol/Statusdat/Component/.StatusdatComponentTest.php.swp b/tests/php/library/Icinga/Protocol/Statusdat/Component/.StatusdatComponentTest.php.swp new file mode 100644 index 000000000..0b7e3013a Binary files /dev/null and b/tests/php/library/Icinga/Protocol/Statusdat/Component/.StatusdatComponentTest.php.swp differ diff --git a/tests/php/library/Icinga/Protocol/Statusdat/Component/StatusdatComponentTest.php b/tests/php/library/Icinga/Protocol/Statusdat/Component/StatusdatComponentTest.php index f78a217d2..c2c615a29 100755 --- a/tests/php/library/Icinga/Protocol/Statusdat/Component/StatusdatComponentTest.php +++ b/tests/php/library/Icinga/Protocol/Statusdat/Component/StatusdatComponentTest.php @@ -1,19 +1,21 @@