Merge pull request #280 from Icinga/fix/broken-routes-if-monitoring-not-installed-279

Split `icingadb` and `monitoring` graph controllers
This commit is contained in:
Johannes Meyer 2022-08-24 14:22:58 +02:00 committed by GitHub
commit 641de8b7e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 328 additions and 73 deletions

View file

@ -2,24 +2,19 @@
namespace Icinga\Module\Graphite\Controllers;
use Icinga\Application\Modules\Module;
use Icinga\Exception\Http\HttpBadRequestException;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Module\Graphite\Graphing\GraphingTrait;
use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport;
use Icinga\Module\Graphite\Util\IcingadbUtils;
use Icinga\Module\Graphite\Web\Controller\MonitoringAwareController;
use Icinga\Module\Graphite\Web\Widget\Graphs;
use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\MonitoredObject;
use Icinga\Module\Monitoring\Object\Service;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\Service;
use Icinga\Web\Controller;
use Icinga\Web\UrlParams;
use Icinga\Module\Icingadb\Model\Service as IcingadbService;
use Icinga\Module\Icingadb\Model\Host as IcingadbHost;
use ipl\Orm\Model;
use ipl\Stdlib\Filter;
class GraphController extends MonitoringAwareController
class GraphController extends Controller
{
use GraphingTrait;
@ -62,64 +57,12 @@ class GraphController extends MonitoringAwareController
}
}
public function hostAction()
{
if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) {
$this->icingadbHost();
return;
}
$hostName = $this->filterParams->getRequired('host.name');
$checkCommandColumn = '_host_' . Graphs::getObscuredCheckCommandCustomVar();
$host = $this->applyMonitoringRestriction(
$this->backend->select()->from('hoststatus', ['host_check_command', $checkCommandColumn])
)
->where('host_name', $hostName)
->limit(1) // just to be sure to save a few CPU cycles
->fetchRow();
if ($host === false) {
throw new HttpNotFoundException('%s', $this->translate('No such host'));
}
$this->supplyImage(new Host($this->backend, $hostName), $host->host_check_command, $host->$checkCommandColumn);
}
public function serviceAction()
{
if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) {
$this->icingadbService();
return;
}
$hostName = $this->filterParams->getRequired('host.name');
$serviceName = $this->filterParams->getRequired('service.name');
$checkCommandColumn = '_service_' . Graphs::getObscuredCheckCommandCustomVar();
$service = $this->applyMonitoringRestriction(
$this->backend->select()->from('servicestatus', ['service_check_command', $checkCommandColumn])
)
->where('host_name', $hostName)
->where('service_description', $serviceName)
->limit(1) // just to be sure to save a few CPU cycles
->fetchRow();
if ($service === false) {
throw new HttpNotFoundException('%s', $this->translate('No such service'));
}
$this->supplyImage(
new Service($this->backend, $hostName, $serviceName),
$service->service_check_command,
$service->$checkCommandColumn
);
}
private function icingadbService()
{
$hostName = $this->filterParams->getRequired('host.name');
$serviceName = $this->filterParams->getRequired('service.name');
$icingadbUtils = IcingadbUtils::getInstance();
$query = IcingadbService::on($icingadbUtils->getDb())
$query = Service::on($icingadbUtils->getDb())
->with('state')
->with('host');
@ -130,7 +73,7 @@ class GraphController extends MonitoringAwareController
$icingadbUtils->applyRestrictions($query);
/** @var IcingadbService $service */
/** @var Service $service */
$service = $query->first();
if ($service === null) {
@ -146,16 +89,16 @@ class GraphController extends MonitoringAwareController
);
}
private function icingadbHost()
public function hostAction()
{
$hostName = $this->filterParams->getRequired('host.name');
$icingadbUtils = IcingadbUtils::getInstance();
$query = IcingadbHost::on($icingadbUtils->getDb())->with('state');
$query = Host::on($icingadbUtils->getDb())->with('state');
$query->filter(Filter::equal('host.name', $hostName));
$icingadbUtils->applyRestrictions($query);
/** @var IcingadbHost $host */
/** @var Host $host */
$host = $query->first();
if ($host === null) {
@ -172,12 +115,12 @@ class GraphController extends MonitoringAwareController
}
/**
* Do all monitored object type independend actions
* Do all monitored object type independent actions
*
* @param MonitoredObject|Model $object The object to render the graphs for
* @param string $checkCommand The check command of the object we supply an image for
* @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we
* display graphs for
* @param Model $object The object to render the graphs for
* @param string $checkCommand The check command of the object we supply an image for
* @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we
* display graphs for
*/
protected function supplyImage($object, $checkCommand, $obscuredCheckCommand)
{

View file

@ -0,0 +1,155 @@
<?php
namespace Icinga\Module\Graphite\Controllers;
use Icinga\Exception\Http\HttpBadRequestException;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Module\Graphite\Graphing\GraphingTrait;
use Icinga\Module\Graphite\Web\Controller\MonitoringAwareController;
use Icinga\Module\Graphite\Web\Widget\Graphs;
use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\MonitoredObject;
use Icinga\Module\Monitoring\Object\Service;
use Icinga\Web\UrlParams;
class MonitoringGraphController extends MonitoringAwareController
{
use GraphingTrait;
/**
* The URL parameters for the graph
*
* @var string[]
*/
protected $graphParamsNames = [
'start', 'end',
'width', 'height',
'legend',
'template', 'default_template',
'bgcolor', 'fgcolor',
'majorGridLineColor', 'minorGridLineColor'
];
/**
* The URL parameters for metrics filtering
*
* @var UrlParams
*/
protected $filterParams;
/**
* The URL parameters for the graph
*
* @var string[]
*/
protected $graphParams = [];
public function init()
{
parent::init();
$this->filterParams = clone $this->getRequest()->getUrl()->getParams();
foreach ($this->graphParamsNames as $paramName) {
$this->graphParams[$paramName] = $this->filterParams->shift($paramName);
}
}
public function hostAction()
{
$hostName = $this->filterParams->getRequired('host.name');
$checkCommandColumn = '_host_' . Graphs::getObscuredCheckCommandCustomVar();
$host = $this->applyMonitoringRestriction(
$this->backend->select()->from('hoststatus', ['host_check_command', $checkCommandColumn])
)
->where('host_name', $hostName)
->limit(1) // just to be sure to save a few CPU cycles
->fetchRow();
if ($host === false) {
throw new HttpNotFoundException('%s', $this->translate('No such host'));
}
$this->supplyImage(new Host($this->backend, $hostName), $host->host_check_command, $host->$checkCommandColumn);
}
public function serviceAction()
{
$hostName = $this->filterParams->getRequired('host.name');
$serviceName = $this->filterParams->getRequired('service.name');
$checkCommandColumn = '_service_' . Graphs::getObscuredCheckCommandCustomVar();
$service = $this->applyMonitoringRestriction(
$this->backend->select()->from('servicestatus', ['service_check_command', $checkCommandColumn])
)
->where('host_name', $hostName)
->where('service_description', $serviceName)
->limit(1) // just to be sure to save a few CPU cycles
->fetchRow();
if ($service === false) {
throw new HttpNotFoundException('%s', $this->translate('No such service'));
}
$this->supplyImage(
new Service($this->backend, $hostName, $serviceName),
$service->service_check_command,
$service->$checkCommandColumn
);
}
/**
* Do all monitored object type independend actions
*
* @param MonitoredObject $object The object to render the graphs for
* @param string $checkCommand The check command of the object we supply an image for
* @param string|null $obscuredCheckCommand The "real" check command (if any) of the object we
* display graphs for
*/
protected function supplyImage($object, $checkCommand, $obscuredCheckCommand)
{
if (isset($this->graphParams['default_template'])) {
$urlParam = 'default_template';
$templates = $this->getAllTemplates()->getDefaultTemplates();
} else {
$urlParam = 'template';
$templates = $this->getAllTemplates()->getTemplates(
$obscuredCheckCommand === null ? $checkCommand : $obscuredCheckCommand
);
}
if (! isset($templates[$this->graphParams[$urlParam]])) {
throw new HttpNotFoundException($this->translate('No such template'));
}
$charts = $templates[$this->graphParams[$urlParam]]->getCharts(
static::getMetricsDataSource(),
$object,
array_map('rawurldecode', $this->filterParams->toArray(false))
);
switch (count($charts)) {
case 0:
throw new HttpNotFoundException($this->translate('No such graph'));
case 1:
$charts[0]
->setFrom($this->graphParams['start'])
->setUntil($this->graphParams['end'])
->setWidth($this->graphParams['width'])
->setHeight($this->graphParams['height'])
->setBackgroundColor($this->graphParams['bgcolor'])
->setForegroundColor($this->graphParams['fgcolor'])
->setMajorGridLineColor($this->graphParams['majorGridLineColor'])
->setMinorGridLineColor($this->graphParams['minorGridLineColor'])
->setShowLegend((bool) $this->graphParams['legend'])
->serveImage($this->getResponse());
// not falling through, serveImage exits
default:
throw new HttpBadRequestException('%s', $this->translate(
'Graphite Web yields more than one metric for the given filter.'
. ' Please specify a more precise filter.'
));
}
}
}

View file

@ -2,10 +2,20 @@
namespace Icinga\Module\Graphite\Web\Controller;
use ArrayIterator;
use Icinga\Application\Modules\Module;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filterable;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\QueryException;
use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport;
use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Module\Monitoring\Data\CustomvarProtectionIterator;
use Icinga\Module\Monitoring\DataView\DataView;
use Icinga\Util\Json;
use Icinga\File\Csv;
use Icinga\Web\Controller;
use Icinga\Web\Url;
abstract class MonitoringAwareController extends Controller
{
@ -34,6 +44,132 @@ abstract class MonitoringAwareController extends Controller
return;
}
parent::moduleInit();
$this->backend = MonitoringBackend::instance($this->_getParam('backend'));
$this->view->url = Url::fromRequest();
}
protected function handleFormatRequest($query)
{
$desiredContentType = $this->getRequest()->getHeader('Accept');
if ($desiredContentType === 'application/json') {
$desiredFormat = 'json';
} elseif ($desiredContentType === 'text/csv') {
$desiredFormat = 'csv';
} else {
$desiredFormat = strtolower($this->params->get('format', 'html'));
}
if ($desiredFormat !== 'html' && ! $this->params->has('limit')) {
$query->limit(); // Resets any default limit and offset
}
switch ($desiredFormat) {
case 'sql':
echo '<pre>'
. htmlspecialchars(wordwrap($query->dump()))
. '</pre>';
exit;
case 'json':
$response = $this->getResponse();
$response
->setHeader('Content-Type', 'application/json')
->setHeader('Cache-Control', 'no-store')
->setHeader(
'Content-Disposition',
'inline; filename=' . $this->getRequest()->getActionName() . '.json'
)
->appendBody(
Json::sanitize(
iterator_to_array(
new CustomvarProtectionIterator(
new ArrayIterator($query->fetchAll())
)
)
)
)
->sendResponse();
exit;
case 'csv':
$response = $this->getResponse();
$response
->setHeader('Content-Type', 'text/csv')
->setHeader('Cache-Control', 'no-store')
->setHeader(
'Content-Disposition',
'attachment; filename=' . $this->getRequest()->getActionName() . '.csv'
)
->appendBody((string) Csv::fromQuery(new CustomvarProtectionIterator($query)))
->sendResponse();
exit;
}
}
/**
* Apply a restriction of the authenticated on the given filterable
*
* @param string $name Name of the restriction
* @param Filterable $filterable Filterable to restrict
*
* @return Filterable The filterable having the restriction applied
*/
protected function applyRestriction($name, Filterable $filterable)
{
$filterable->applyFilter($this->getRestriction($name));
return $filterable;
}
/**
* Get a restriction of the authenticated
*
* @param string $name Name of the restriction
*
* @return Filter Filter object
* @throws ConfigurationError If the restriction contains invalid filter columns
*/
protected function getRestriction($name)
{
$restriction = Filter::matchAny();
$restriction->setAllowedFilterColumns(array(
'host_name',
'hostgroup_name',
'instance_name',
'service_description',
'servicegroup_name',
function ($c) {
return preg_match('/^_(?:host|service)_/i', $c);
}
));
foreach ($this->getRestrictions($name) as $filter) {
if ($filter === '*') {
return Filter::matchAll();
}
try {
$restriction->addFilter(Filter::fromQueryString($filter));
} catch (QueryException $e) {
throw new ConfigurationError(
$this->translate(
'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s'
),
$name,
$filter,
implode(', ', array(
'instance_name',
'host_name',
'hostgroup_name',
'service_description',
'servicegroup_name',
'_(host|service)_<customvar-name>'
)),
$e
);
}
}
if ($restriction->isEmpty()) {
return Filter::matchAll();
}
return $restriction;
}
}

21
run.php
View file

@ -2,6 +2,8 @@
/** @var \Icinga\Application\Modules\Module $this */
use Icinga\Module\Graphite\ProvidedHook\Icingadb\IcingadbSupport;
require_once $this->getLibDir() . '/vendor/Psr/Loader.php';
require_once $this->getLibDir() . '/vendor/iplx/Loader.php';
@ -9,3 +11,22 @@ $this->provideHook('monitoring/DetailviewExtension');
$this->provideHook('icingadb/IcingadbSupport');
$this->provideHook('icingadb/HostDetailExtension');
$this->provideHook('icingadb/ServiceDetailExtension');
if (! $this->exists('icingadb') || ! IcingadbSupport::useIcingaDbAsBackend()) {
$this->addRoute('graphite/monitoring-graph/host', new Zend_Controller_Router_Route(
'graphite/graph/host',
[
'controller' => 'monitoring-graph',
'action' => 'host',
'module' => 'graphite'
]
));
$this->addRoute('graphite/monitoring-graph/service', new Zend_Controller_Router_Route(
'graphite/graph/service',
[
'controller' => 'monitoring-graph',
'action' => 'service',
'module' => 'graphite'
]
));
}