Add tested Statusdat Protocol

In order to run the tests, phpunit must be called in the
tests/php folder

refs #4212
This commit is contained in:
Jannis Moßhammer 2013-06-03 16:14:46 +02:00
parent 66b8f70e3e
commit ba38c89755
24 changed files with 1491 additions and 27 deletions

View File

@ -0,0 +1,152 @@
<?php
namespace Icinga\Application;
class Logger
{
const DEFAULT_LOG_TYPE = "stream";
const DEFAULT_LOG_TARGET = "./var/log/icinga.log";
const DEFAULT_DEBUG_TARGET = "./var/log/icinga.debug.log";
private $writers = array();
private $logger = null;
private static $instance;
private static $queue = array();
public function __construct(\Zend_Config $config)
{
$this->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;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Exception;
class ConfigurationError extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Icinga\Exception;
use RuntimeException;
class MissingParameterException extends RuntimeException
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Exception;
class NotImplementedError extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Exception;
class ProgrammingError extends \Exception
{
}

View File

@ -0,0 +1,50 @@
<?php
namespace Icinga\Protocol;
abstract class AbstractQuery
{
const SORT_ASC = 1;
const SORT_DESC = -1;
abstract public function where($key, $val = null);
abstract public function order($col);
abstract public function limit($count = null, $offset = null);
abstract public function from($table, $columns = null);
public function hasOrder()
{
return false;
}
public function hasColumns()
{
return false;
}
public function getColumns()
{
return array();
}
public function hasLimit()
{
return false;
}
public function hasOffset()
{
return false;
}
public function getLimit()
{
return null;
}
public function getOffset()
{
return null;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Icinga\Protocol\Statusdat\Exception;
class ParsingException extends \RuntimeException
{
}

View File

@ -0,0 +1,18 @@
<?php
/**
* Created by JetBrains PhpStorm.
* User: moja
* Date: 1/17/13
* Time: 10:21 AM
* To change this template use File | Settings | File Templates.
*/
namespace Icinga\Protocol\Statusdat;
interface IReader
{
public function getState();
public function getObjects();
public function getObjectByName($type,$name);
}

View File

@ -0,0 +1,264 @@
<?php
namespace Icinga\Protocol\Statusdat;
use Icinga\Protocol\Statusdat\Exception\ParsingException as ParsingException;
class Parser
{
private $deferred = array();
private $filehandle = null;
private $currentObjectType = null;
private $currentStateType = null;
private $icingaState = null;
private $lineCtr = 0;
public function __construct($filehandle = null, $baseState = null)
{
if (!is_resource($filehandle))
throw new \Icinga\Exception\ConfigurationError("Statusdat parser can't find $filehandle");
$this->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;$i<count($members);$i+=2) {
$res[] = $members[$i].";".$members[$i+1];
}
return $res;
} else {
return $members;
}
}
protected function getObjectIdentifier(&$object)
{
if ($this->currentObjectType == "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;
}
}

View File

@ -0,0 +1,256 @@
<?php
namespace Icinga\Protocol\Statusdat;
use Icinga\Protocol;
class Query extends Protocol\AbstractQuery
{
public static $VALID_TARGETS = array(
"hosts" => 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;
}
}

View File

@ -0,0 +1,216 @@
<?php
namespace Icinga\Protocol\Statusdat\Query;
class Expression implements IQueryPart
{
const ENC_NUMERIC = 0;
const ENC_SET = 0;
const ENC_STRING = 0;
private $expression;
private $field = null;
private $basedata = array();
private $function = null;
private $value = "";
private $operator = null;
private $name = null;
public $CB = null;
private function getOperatorType($token)
{
switch (strtoupper($token)) {
case ">":
$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;
}
}

View File

@ -0,0 +1,226 @@
<?php
namespace Icinga\Protocol\Statusdat\Query;
class Group implements IQueryPart
{
const GROUP_BEGIN = "(";
const GROUP_END = ")";
const CONJUNCTION_AND = "AND ";
const CONJUNCTION_OR = "OR ";
const EXPRESSION = 0;
const EOF = -1;
const TYPE_AND = "AND";
const TYPE_OR = "OR";
private $items = array();
private $parsePos = 0;
private $expression = "";
private $expressionClass = null;
private $type = "";
private $subExpressionStart = 0;
private $subExpressionLength = 0;
private $value;
public function setValue($value)
{
$this->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;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Icinga\Protocol\Statusdat\Query;
interface IQueryPart
{
public function __construct($expression = null,&$value = array());
public function filter(array &$base, &$idx=null);
}

View File

@ -0,0 +1,194 @@
<?php
namespace Icinga\Protocol\Statusdat;
use Icinga\Exception as Exception;
use Icinga\Benchmark as Benchmark;
class ObjectContainer extends \stdClass {
public $ref;
public $reader;
public function __construct(\stdClass &$obj,IReader &$reader) {
$this->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));
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Icinga\Protocol\Statusdat;
class RuntimeStateContainer extends \stdClass
{
public $runtimeState = "";
public function __construct($str = "") {
$this->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;
}
}

View File

@ -1,19 +1,21 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/Statusdat/IReader.php");
require_once("../library/Icinga/Protocol/Statusdat/Reader.php");
require_once("../library/Icinga/Protocol/Statusdat/Exception/ParsingException.php");
require_once("../library/Icinga/Exception/ProgrammingError.php");
require_once("../library/Icinga/Protocol/Statusdat/Parser.php");
require_once("../library/Icinga/Protocol/AbstractQuery.php");
require_once("../library/Icinga/Protocol/Statusdat/Query.php");
require_once("../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../library/Icinga/Protocol/Statusdat/Query/Group.php");
require_once("../library/Icinga/Protocol/Statusdat/Query/Expression.php");
require_once("../library/Icinga/Exception/ConfigurationError.php");
require_once("Zend/Config.php");
require_once("Zend/Log.php");
require_once("../../library/Icinga/Protocol/Statusdat/IReader.php");
require_once("../../library/Icinga/Protocol/Statusdat/Reader.php");
require_once("../../library/Icinga/Protocol/Statusdat/Exception/ParsingException.php");
require_once("../../library/Icinga/Exception/ProgrammingError.php");
require_once("../../library/Icinga/Protocol/Statusdat/Parser.php");
require_once("../../library/Icinga/Protocol/AbstractQuery.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/Group.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/Expression.php");
require_once("../../library/Icinga/Exception/ConfigurationError.php");
require_once("../../library/Icinga/Application/Logger.php");
use \Icinga\Protocol\Statusdat as SD;
/**

View File

@ -1,9 +1,9 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/Statusdat/Exception/ParsingException.php");
require_once("../library/Icinga/Exception/ProgrammingError.php");
require_once("../library/Icinga/Protocol/Statusdat/Parser.php");
require_once("../../library/Icinga/Protocol/Statusdat/Exception/ParsingException.php");
require_once("../../library/Icinga/Exception/ProgrammingError.php");
require_once("../../library/Icinga/Protocol/Statusdat/Parser.php");
use Icinga\Protocol\Statusdat\Parser;
/**
*

View File

@ -2,8 +2,8 @@
namespace Tests\Icinga\Protocol\Statusdat\Query;
require_once("../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../library/Icinga/Protocol/Statusdat/Query/Expression.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/Expression.php");
use Icinga\Protocol\Statusdat\Query\Expression;

View File

@ -1,8 +1,8 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat\Query;
require_once("../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../library/Icinga/Protocol/Statusdat/Query/Group.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/IQueryPart.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query/Group.php");
/**
*

View File

@ -1,8 +1,8 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/AbstractQuery.php");
require_once("../library/Icinga/Protocol/Statusdat/Query.php");
require_once("../../library/Icinga/Protocol/AbstractQuery.php");
require_once("../../library/Icinga/Protocol/Statusdat/Query.php");
require_once(dirname(__FILE__)."/ReaderMock.php");

View File

@ -1,6 +1,6 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/Statusdat/IReader.php");
require_once("../../library/Icinga/Protocol/Statusdat/IReader.php");
use Icinga\Protocol\Statusdat\IReader;
@ -39,4 +39,4 @@ class ReaderMock implements IReader
return null;
}
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/Statusdat/IReader.php");
require_once("../library/Icinga/Protocol/Statusdat/Reader.php");
require_once("../library/Icinga/Exception/ConfigurationError.php");
require_once("../../library/Icinga/Protocol/Statusdat/IReader.php");
require_once("../../library/Icinga/Protocol/Statusdat/Reader.php");
require_once("../../library/Icinga/Exception/ConfigurationError.php");
use Icinga\Protocol\Statusdat\Reader as Reader;
define("APPLICATION_PATH","./"); // TODO: test boostrap

View File

@ -2,7 +2,7 @@
namespace Tests\Icinga\Protocol\Statusdat;
require_once("../library/Icinga/Protocol/Statusdat/RuntimeStateContainer.php");
require_once("../../library/Icinga/Protocol/Statusdat/RuntimeStateContainer.php");
class RuntimestatecontainerTest extends \PHPUnit_Framework_TestCase
{