From fe7b5340abac6a0144c53151641dc4ecf6df4231 Mon Sep 17 00:00:00 2001 From: MoreAmazingNick Date: Tue, 23 Jan 2024 17:28:02 +0100 Subject: [PATCH 1/2] feature/icingadb-support-for-monitoring-integration --- .../IcingadbObjectFinder.php | 120 ++++++++++++++++++ .../MonitoredObjectFinder.php | 2 +- .../Icingadb/HostDetailExtension.php | 105 +++++++++++++++ .../Web/Form/MonitoringConnectionForm.php | 112 ++++++++++++---- run.php | 13 +- 5 files changed, 326 insertions(+), 26 deletions(-) create mode 100644 library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php create mode 100644 library/Vspheredb/ProvidedHook/Icingadb/HostDetailExtension.php diff --git a/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php b/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php new file mode 100644 index 00000000..2607a1af --- /dev/null +++ b/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php @@ -0,0 +1,120 @@ +db = $db; + $this->lookup = new CheckRelatedLookup($this->db); + $this->connections = $this->fetchConnections(); + } + + /** + * @param Host $object + * @return HostSystem|VirtualMachine|null + */ + public function find(Host $object) + { + if (! $object instanceof Host) { + return null; + } + + foreach ($this->connections as $row) { + try { + $filter = $this->filterFromRow($object, 'host', $row); + if ($filter && $host = $this->loadOptionalObject('HostSystem', $filter)) { + return $host; + } + $filter = $this->filterFromRow($object, 'vm', $row); + if ($filter && $vm = $this->loadOptionalObject('VirtualMachine', $filter)) { + return $vm; + } + } catch (InvalidPropertyException $e) { + // Shows problems when accessing MonitoredObjectProperties + continue; + } + } + + return null; + } + + /** + * @param string $type + * @param array $filter + * @return HostSystem|VirtualMachine|null + */ + protected function loadOptionalObject($type, $filter) + { + try { + $object = $this->lookup->findOneBy($type, $filter); + assert($object instanceof HostSystem || $object instanceof VirtualMachine); + return $object; + } catch (NotFoundError $e) { + return null; + } + } + + /** + * @param Host $object + * @param string $prefix + * @param \stdClass $row + * @return array|null + */ + protected function filterFromRow(Host $object, $prefix, $row) + { + $filter = []; + $filterPrefix = $prefix === 'vm' ? 'virtual_machine' : 'host_system'; + if ($row->vcenter_uuid !== null) { + $filter[$filterPrefix . '.vcenter_uuid'] = $row->vcenter_uuid; + } + $monPrefix = $prefix === 'vm' ? 'vm_host' : 'host'; + $monProperty = $row->{"monitoring_{$monPrefix}_property"}; + if ($monProperty === null) { + return null; + } + + if (preg_match('/^vars./', $monProperty)) { + $varName = substr($monProperty, 5); + $var = $object->customvar->filter(Filter::equal('name', $varName))->first(); + if ($var != null) { + $value = trim($var->value,'"'); + } else { + $value = null; + } + } else { + $value = $object->{$monProperty}; + } + + if ($value === null) { + return null; + } + $filter[$row->{"{$prefix}_property"}] = $value; + + return $filter; + } + + protected function fetchConnections() + { + return $this->db->getDbAdapter()->fetchAll( + $this->db->getDbAdapter()->select()->from('monitoring_connection')->where('source_type = ?','icingadb')->order('priority DESC') + ); + } +} diff --git a/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php b/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php index ccd577ef..d9a41aca 100644 --- a/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php +++ b/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php @@ -114,7 +114,7 @@ protected function filterFromRow(MonitoredObject $object, $prefix, $row) protected function fetchConnections() { return $this->db->getDbAdapter()->fetchAll( - $this->db->getDbAdapter()->select()->from('monitoring_connection')->order('priority DESC') + $this->db->getDbAdapter()->select()->from('monitoring_connection')->where('source_type = ?','ido')->order('priority DESC') ); } } diff --git a/library/Vspheredb/ProvidedHook/Icingadb/HostDetailExtension.php b/library/Vspheredb/ProvidedHook/Icingadb/HostDetailExtension.php new file mode 100644 index 00000000..252aa77e --- /dev/null +++ b/library/Vspheredb/ProvidedHook/Icingadb/HostDetailExtension.php @@ -0,0 +1,105 @@ +finder = new IcingadbObjectFinder(Db::newConfiguredInstance()); + } + + public function getHtmlForObject(Host $object) :ValidHtml + { + + $vObject = $this->finder->find($object); + if ($vObject instanceof HostSystem) { + $container = $this->container(); + $container->add($this->renderHostSystem($vObject, $object)); + return $container; + } + if ($vObject instanceof VirtualMachine) { + $container = $this->container(); + $container->add($this->renderVirtualMachine($vObject, $object)); + return $container; + } + + return Html::tag('a'); + } + + protected function container() + { + return Html::tag('div', ['class' => [ + 'icinga-module', + 'module-vspheredb', + 'vspheredb-monitoring-integration' + ]]); + } + + protected function renderHostSystem(HostSystem $host, Host $object) + { + $stats = HostQuickStats::loadFor($host); + $cpu = new CpuAbsoluteUsage($stats->get('overall_cpu_usage'), $host->get('hardware_cpu_cores')); + $mem = new MemoryUsage($stats->get('overall_memory_usage_mb'), $host->get('hardware_memory_size_mb')); + + return [ + Html::tag('h2', null, $this->linkToObject($host, $object)), + Html::tag('div', ['class' => 'monitoring-integration-details'])->add([$mem, $cpu]), + ]; + } + + protected function renderVirtualMachine(VirtualMachine $vm, Host $object) + { + $stats = VmQuickStats::loadFor($vm); + $cpu = new CpuAbsoluteUsage($stats->get('overall_cpu_usage')); // $vm->get('hardware_numcpu') + $mem = new MemoryUsage( + $stats->get('guest_memory_usage_mb'), + $vm->get('hardware_memorymb'), + $stats->get('host_memory_usage_mb') + ); + + return [ + Html::tag('h2', null, $this->linkToObject($vm, $object)), + Html::tag('div', ['class' => 'monitoring-integration-details'])->add([$mem, $cpu]), + ]; + } + + protected function linkToObject($vObject, Host $object) + { + if ($vObject instanceof HostSystem) { + $label = mt('vspheredb', 'ESXi Host: %s'); + $url = 'vspheredb/host'; + } elseif ($vObject instanceof VirtualMachine) { + $label = mt('vspheredb', 'Virtual Machine: %s'); + $url = 'vspheredb/vm'; + } else { + throw new \RuntimeException(sprintf('Unable to link to %s', get_class($vObject))); + } + + return Link::create( + sprintf($label, $vObject->object()->get('object_name')), + $url, + ['uuid' => Util::niceUuid($vObject->uuid), 'monitoringObject' => $object->name] + ); + } +} diff --git a/library/Vspheredb/Web/Form/MonitoringConnectionForm.php b/library/Vspheredb/Web/Form/MonitoringConnectionForm.php index a16052c5..9541576b 100644 --- a/library/Vspheredb/Web/Form/MonitoringConnectionForm.php +++ b/library/Vspheredb/Web/Form/MonitoringConnectionForm.php @@ -47,7 +47,7 @@ protected function assemble() 'options' => $this->optionalEnum([ 'ido' => $this->translate('IDO'), // 'icinga2-api' => $this->translate('Icinga 2 API'), - // 'icingadb' => $this->translate('IcingaDB'), + 'icingadb' => $this->translate('IcingaDB'), ]), 'class' => 'autosubmit', ]); @@ -55,13 +55,19 @@ protected function assemble() if (! $sourceType) { return; } - - $this->addElement('select', 'source_resource_name', [ - 'label' => $this->translate('IDO Resource'), - 'options' => $this->optionalEnum($this->enumIdoResourceNames()), - 'class' => 'autosubmit', - ]); - + if($sourceType === 'ido'){ + $this->addElement('select', 'source_resource_name', [ + 'label' => $this->translate('IDO Resource'), + 'options' => $this->optionalEnum($this->enumIdoResourceNames()), + 'class' => 'autosubmit', + ]); + }elseif($sourceType === 'icingadb'){ + $this->addElement('select', 'source_resource_name', [ + 'label' => $this->translate('Icingadb Resource'), + 'options' => $this->optionalEnum($this->enumIcingadbResourceNames()), + 'class' => 'autosubmit', + ]); + } $resourceName = $this->getElement('source_resource_name')->getValue(); if (! $resourceName) { $this->addElement('submit', 'submit', [ @@ -69,23 +75,46 @@ protected function assemble() ]); return; } + if($sourceType === 'ido'){ + try { + $resource = ResourceFactory::create($resourceName); + if ($resource instanceof DbConnection) { + $idoVars = $this->enumIdoCustomVars($resource); + } else { + throw new InvalidArgumentException("Resource '$resourceName' is not a DbConnection"); + } + } catch (\Exception $e) { + $this->getElement('source_resource_name')->addMessage($e->getMessage()); + return; + } - try { - $resource = ResourceFactory::create($resourceName); - if ($resource instanceof DbConnection) { - $idoVars = $this->enumIdoCustomVars($resource); - } else { - throw new InvalidArgumentException("Resource '$resourceName' is not a DbConnection"); + $varOptions = $this->optionalEnum([ + 'host_name' => $this->translate('Hostname'), + 'display_name' => $this->translate('Display Name'), + 'address' => $this->translate('Address'), + ] + [$this->translate('Custom Variables') => $idoVars]); + }elseif($sourceType === 'icingadb'){ + try { + $resource = ResourceFactory::create($resourceName); + if ($resource instanceof DbConnection) { + $icingadbVars = $this->enumIcingadbCustomVars($resource); + } else { + throw new InvalidArgumentException("Resource '$resourceName' is not a DbConnection"); + } + } catch (\Exception $e) { + $this->getElement('source_resource_name')->addMessage($e->getMessage()); + return; } - } catch (\Exception $e) { - $this->getElement('source_resource_name')->addMessage($e->getMessage()); - return; + + $varOptions = $this->optionalEnum([ + 'name' => $this->translate('Hostname'), + 'display_name' => $this->translate('Display Name'), + 'address' => $this->translate('Address v4'), + 'address6' => $this->translate('Address v6'), + ] + [$this->translate('Custom Variables') => $icingadbVars]); } - $idoOptions = $this->optionalEnum([ - 'host_name' => $this->translate('Hostname'), - 'display_name' => $this->translate('Display Name'), - 'address' => $this->translate('Address'), - ] + [$this->translate('Custom Variables') => $idoVars]); + + $this->add(Html::tag('h2', $this->translate('Host Systems'))); $this->add(Html::tag('p', $this->translate( @@ -109,7 +138,7 @@ protected function assemble() 'description' => $this->translate( 'Property of the Host System (as known by Icinga)' ), - 'options' => $idoOptions, + 'options' => $varOptions, ]); $this->add(Html::tag('h2', $this->translate('Virtual Machines'))); @@ -133,7 +162,7 @@ protected function assemble() 'description' => $this->translate( 'Property of the Virtual Machine (as known by Icinga)' ), - 'options' => $idoOptions, + 'options' => $varOptions, ]); $submit = new SubmitElement('submit', [ @@ -190,6 +219,34 @@ public function onSuccess() } } + protected function enumIcingadbCustomVars(DbConnection $db) + { + $dba = $db->getDbAdapter(); + + $vars = $dba->fetchPairs( + $dba->select()->from( + ['cvs' => 'customvar'], + [ + 'varname' => 'cvs.name', + 'varcount' => 'COUNT(*)' + ] + )->join( + ['o' => 'host_customvar'], + 'o.customvar_id = cvs.id', + [] + ) + ->group('varname') + ->order('varname') + ); + + $result = []; + foreach ($vars as $name => $count) { + $result["vars.$name"] = "vars.$name ({$count}x)"; + } + + return $result; + } + protected function enumIdoCustomVars(DbConnection $db) { $dba = $db->getDbAdapter(); @@ -255,7 +312,14 @@ protected function enumIdoResourceNames(): array return $resources; } + protected function enumIcingadbResourceNames(): array + { + $resources = []; + $resourceName = Config::module('icingadb')->get('icingadb','resource'); + $resources[$resourceName] = $resourceName; + return $resources; + } protected function enumVCenters(): array { return $this->makeNiceUuidKeys($this->db->fetchPairs( diff --git a/run.php b/run.php index f8d18a74..9a043001 100644 --- a/run.php +++ b/run.php @@ -1,5 +1,6 @@ provideHook('director/ImportSource'); $this->provideHook('director/DataType', DataTypeMonitoringRule::class); $this->provideHook('vspheredb/PerfDataConsumer', PerfDataConsumerInfluxDb::class); -$this->provideHook('monitoring/DetailviewExtension'); + + + +if (Module::exists('monitoring') ) { + $this->provideHook('monitoring/DetailviewExtension'); + +} +if (Module::exists('icingadb') ) { + $this->provideHook('icingadb/HostDetailExtension'); + +} \ No newline at end of file From 98c1c5af9961740e2e35c1f15499a85d9e8e177e Mon Sep 17 00:00:00 2001 From: MoreAmazingNick Date: Tue, 23 Jan 2024 18:00:00 +0100 Subject: [PATCH 2/2] fix/code-style-icingadb-support --- .../IcingadbIntegration/IcingadbObjectFinder.php | 6 ++++-- .../MonitoringIntegration/MonitoredObjectFinder.php | 4 +++- .../Vspheredb/Web/Form/MonitoringConnectionForm.php | 10 +++++----- run.php | 8 +++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php b/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php index 2607a1af..dc0700fe 100644 --- a/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php +++ b/library/Vspheredb/IcingadbIntegration/IcingadbObjectFinder.php @@ -95,7 +95,7 @@ protected function filterFromRow(Host $object, $prefix, $row) $varName = substr($monProperty, 5); $var = $object->customvar->filter(Filter::equal('name', $varName))->first(); if ($var != null) { - $value = trim($var->value,'"'); + $value = trim($var->value, '"'); } else { $value = null; } @@ -114,7 +114,9 @@ protected function filterFromRow(Host $object, $prefix, $row) protected function fetchConnections() { return $this->db->getDbAdapter()->fetchAll( - $this->db->getDbAdapter()->select()->from('monitoring_connection')->where('source_type = ?','icingadb')->order('priority DESC') + $this->db->getDbAdapter()->select()->from('monitoring_connection') + ->where('source_type = ?', 'icingadb') + ->order('priority DESC') ); } } diff --git a/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php b/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php index d9a41aca..c6699a93 100644 --- a/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php +++ b/library/Vspheredb/MonitoringIntegration/MonitoredObjectFinder.php @@ -114,7 +114,9 @@ protected function filterFromRow(MonitoredObject $object, $prefix, $row) protected function fetchConnections() { return $this->db->getDbAdapter()->fetchAll( - $this->db->getDbAdapter()->select()->from('monitoring_connection')->where('source_type = ?','ido')->order('priority DESC') + $this->db->getDbAdapter()->select()->from('monitoring_connection') + ->where('source_type = ?', 'ido') + ->order('priority DESC') ); } } diff --git a/library/Vspheredb/Web/Form/MonitoringConnectionForm.php b/library/Vspheredb/Web/Form/MonitoringConnectionForm.php index 9541576b..bcf49967 100644 --- a/library/Vspheredb/Web/Form/MonitoringConnectionForm.php +++ b/library/Vspheredb/Web/Form/MonitoringConnectionForm.php @@ -55,13 +55,13 @@ protected function assemble() if (! $sourceType) { return; } - if($sourceType === 'ido'){ + if ($sourceType === 'ido') { $this->addElement('select', 'source_resource_name', [ 'label' => $this->translate('IDO Resource'), 'options' => $this->optionalEnum($this->enumIdoResourceNames()), 'class' => 'autosubmit', ]); - }elseif($sourceType === 'icingadb'){ + } elseif ($sourceType === 'icingadb') { $this->addElement('select', 'source_resource_name', [ 'label' => $this->translate('Icingadb Resource'), 'options' => $this->optionalEnum($this->enumIcingadbResourceNames()), @@ -75,7 +75,7 @@ protected function assemble() ]); return; } - if($sourceType === 'ido'){ + if ($sourceType === 'ido') { try { $resource = ResourceFactory::create($resourceName); if ($resource instanceof DbConnection) { @@ -93,7 +93,7 @@ protected function assemble() 'display_name' => $this->translate('Display Name'), 'address' => $this->translate('Address'), ] + [$this->translate('Custom Variables') => $idoVars]); - }elseif($sourceType === 'icingadb'){ + } elseif ($sourceType === 'icingadb') { try { $resource = ResourceFactory::create($resourceName); if ($resource instanceof DbConnection) { @@ -315,7 +315,7 @@ protected function enumIdoResourceNames(): array protected function enumIcingadbResourceNames(): array { $resources = []; - $resourceName = Config::module('icingadb')->get('icingadb','resource'); + $resourceName = Config::module('icingadb')->get('icingadb', 'resource'); $resources[$resourceName] = $resourceName; return $resources; diff --git a/run.php b/run.php index 9a043001..32a2006f 100644 --- a/run.php +++ b/run.php @@ -18,11 +18,9 @@ -if (Module::exists('monitoring') ) { +if (Module::exists('monitoring')) { $this->provideHook('monitoring/DetailviewExtension'); - } -if (Module::exists('icingadb') ) { +if (Module::exists('icingadb')) { $this->provideHook('icingadb/HostDetailExtension'); - -} \ No newline at end of file +}