From 6bea1eff4149148f6b4bca8132ccbb2e63997883 Mon Sep 17 00:00:00 2001 From: Marc DeTrano Date: Fri, 16 Sep 2016 16:25:11 -0600 Subject: [PATCH] Implement Icinga Dependency Configuration. --- application/clicommands/DependencyCommand.php | 15 + .../controllers/DependenciesController.php | 9 + .../controllers/DependencyController.php | 60 +++ .../DependencytemplateController.php | 16 + application/controllers/SuggestController.php | 83 ++++ application/forms/IcingaDependencyForm.php | 252 +++++++++++ application/tables/IcingaDependencyTable.php | 105 +++++ .../tables/IcingaDependencyTemplateTable.php | 13 + .../Dashlet/DependencyObjectDashlet.php | 26 ++ .../Director/Dashboard/ObjectsDashboard.php | 1 + library/Director/Db.php | 1 + .../Director/IcingaConfig/IcingaConfig.php | 4 +- library/Director/Objects/IcingaDependency.php | 421 ++++++++++++++++++ library/Director/Objects/IcingaObject.php | 3 +- .../Web/ActionBar/TemplateActionBar.php | 3 +- .../Web/Controller/ObjectsController.php | 12 +- .../Web/Controller/TemplateController.php | 2 +- .../Director/Web/Form/DirectorObjectForm.php | 41 +- library/Director/Web/Tabs/ObjectsTabs.php | 16 +- public/js/module.js | 3 +- .../mysql-migrations/upgrade_dependencies.sql | 88 ++++ .../upgrade_dependencies_2.sql | 2 + schema/mysql.sql | 88 ++++ 23 files changed, 1226 insertions(+), 38 deletions(-) create mode 100644 application/clicommands/DependencyCommand.php create mode 100644 application/controllers/DependenciesController.php create mode 100644 application/controllers/DependencyController.php create mode 100644 application/controllers/DependencytemplateController.php create mode 100644 application/forms/IcingaDependencyForm.php create mode 100644 application/tables/IcingaDependencyTable.php create mode 100644 application/tables/IcingaDependencyTemplateTable.php create mode 100644 library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php create mode 100644 library/Director/Objects/IcingaDependency.php create mode 100644 schema/mysql-migrations/upgrade_dependencies.sql create mode 100644 schema/mysql-migrations/upgrade_dependencies_2.sql diff --git a/application/clicommands/DependencyCommand.php b/application/clicommands/DependencyCommand.php new file mode 100644 index 00000000..ff5cbdc6 --- /dev/null +++ b/application/clicommands/DependencyCommand.php @@ -0,0 +1,15 @@ +params->get('apply')) { + $this->apply = IcingaDependency::load( + array('object_name' => $apply, 'object_type' => 'template'), + $this->db() + ); + } + + } + + protected function loadObject() + { + if ($this->object === null) { + if ($name = $this->params->get('name')) { + $params = array('object_name' => $name); + $db = $this->db(); + + $this->object = IcingaDependency::load($params, $db); + } else { + parent::loadObject(); + } + } + + return $this->object; + } + + public function loadForm($name) + { + $form = parent::loadForm($name); + return $form; + } + + protected function beforeHandlingAddRequest($form) + { + if ($this->apply) { + $form->createApplyRuleFor($this->apply); + } + } + + + +} diff --git a/application/controllers/DependencytemplateController.php b/application/controllers/DependencytemplateController.php new file mode 100644 index 00000000..e2bc49d1 --- /dev/null +++ b/application/controllers/DependencytemplateController.php @@ -0,0 +1,16 @@ + $this->params->get('name') + ], $this->db()); + } +} diff --git a/application/controllers/SuggestController.php b/application/controllers/SuggestController.php index dad12236..cc338155 100644 --- a/application/controllers/SuggestController.php +++ b/application/controllers/SuggestController.php @@ -6,6 +6,7 @@ use Icinga\Module\Director\Objects\IcingaService; use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Data\Filter\Filter; use ipl\Html\Util; +use Icinga\Module\Director\Objects\HostApplyMatches; class SuggestController extends ActionController { @@ -91,6 +92,48 @@ class SuggestController extends ActionController return $db->fetchCol($query); } + protected function suggestServicenames() + { + $r=array(); + $this->assertPermission('director/services'); + $db = $this->db()->getDbAdapter(); + $for_host = $this->getRequest()->getPost('for_host'); + if (!empty($for_host)) { + $tmp_host = IcingaHost::load($for_host,$this->db()); + } + + $query = $db->select()->distinct() + ->from('icinga_service', 'object_name') + ->order('object_name') + ->where("object_type IN ('object','apply')"); + if (!empty($tmp_host)) { + $query->where('host_id = ?',$tmp_host->id); + } + $r = array_merge($r,$db->fetchCol($query)); + if (!empty($tmp_host)) { + $resolver = $tmp_host->templateResolver(); + foreach ($resolver->fetchResolvedParents() as $template_obj) { + $query = $db->select()->distinct() + ->from('icinga_service', 'object_name') + ->order('object_name') + ->where("object_type IN ('object','apply')") + ->where('host_id = ?', $template_obj->id); + $r = array_merge($r,$db->fetchCol($query)); + } + + $matcher = HostApplyMatches::prepare($tmp_host); + foreach ($this->getAllApplyRules() as $rule) { + if ($matcher->matchesFilter($rule->filter)) { //TODO + $r[]=$rule->name; + } + } + + } + natcasesort($r); + return $r; + } + + protected function suggestHosttemplates() { $this->assertPermission('director/hosts'); @@ -199,6 +242,17 @@ class SuggestController extends ActionController return $res; } + protected function suggestDependencytemplates() + { + $this->assertPermission('director/hosts'); + $db = $this->db()->getDbAdapter(); + $query = $db->select() + ->from('icinga_dependency', 'object_name') + ->order('object_name') + ->where("object_type = 'template'"); + return $db->fetchCol($query); + } + protected function highlight($val, $search) { $search = ($search); @@ -209,4 +263,33 @@ class SuggestController extends ActionController $val ); } + + protected function getAllApplyRules() + { + $allApplyRules=$this->fetchAllApplyRules(); + foreach ($allApplyRules as $rule) { + $rule->filter = Filter::fromQueryString($rule->assign_filter); + } + + return $allApplyRules; + } + + protected function fetchAllApplyRules() + { + $db = $this->db()->getDbAdapter(); + $query = $db->select()->from( + array('s' => 'icinga_service'), + array( + 'id' => 's.id', + 'name' => 's.object_name', + 'assign_filter' => 's.assign_filter', + ) + )->where('object_type = ? AND assign_filter IS NOT NULL', 'apply'); + + return $db->fetchAll($query); + } + + + + } diff --git a/application/forms/IcingaDependencyForm.php b/application/forms/IcingaDependencyForm.php new file mode 100644 index 00000000..b9967196 --- /dev/null +++ b/application/forms/IcingaDependencyForm.php @@ -0,0 +1,252 @@ +setupDependencyElements(); + } + + protected function setupDependencyElements() { + + $this->addObjectTypeElement(); + if (! $this->hasObjectType()) { + $this->groupMainProperties(); + return; + } + + $this->addNameElement() + ->addDisabledElement() + ->addImportsElement() + ->addObjectsElement() + ->addBooleanElements() + ->addPeriodElement() + ->addAssignmentElements() + ->addEventFilterElements(array('states')) + ->groupMainProperties() + ->setButtons(); + } + + protected function addNameElement() + { + $this->addElement('text', 'object_name', array( + 'label' => $this->translate('Name'), + 'required' => true, + 'description' => $this->translate('Name for the Icinga dependency you are going to create') + )); + + return $this; + } + + + protected function addAssignmentElements() + { + if (!$this->object || !$this->object->isApplyRule()) { + return $this; + } + + $this->addElement('select', 'apply_to', array( + 'label' => $this->translate('Apply to'), + 'description' => $this->translate( + 'Whether this dependency should affect hosts or services' + ), + 'required' => true, + 'class' => 'autosubmit', + 'multiOptions' => $this->optionalEnum( + array( + 'host' => $this->translate('Hosts'), + 'service' => $this->translate('Services'), + ) + ) + )); + + $applyTo = $this->getSentOrObjectValue('apply_to'); + + if ($applyTo === 'host') { + $columns = IcingaHost::enumProperties($this->db, 'host.'); + } elseif ($applyTo === 'service') { + // TODO: Also add host properties + $columns = IcingaService::enumProperties($this->db, 'service.'); + } else { + return $this; + } + + $this->addAssignFilter(array( + 'columns' => $columns, + 'required' => true, + 'description' => $this->translate( + 'This allows you to configure an assignment filter. Please feel' + . ' free to combine as many nested operators as you want' + ) + )); + return $this; + } + + protected function addPeriodElement() + { + $periods = $this->db->enumTimeperiods(); + if (empty($periods)) { + return $this; + } + + $this->addElement( + 'select', + 'period_id', + array( + 'label' => $this->translate('Time period'), + 'description' => $this->translate( + 'The name of a time period which determines when this' + . ' notification should be triggered. Not set by default.' + ), + 'multiOptions' => $this->optionalEnum($periods), + ) + ); + + return $this; + } + + protected function addBooleanElements() { + + $this->addBoolean( + 'disable_checks', + array( + 'label' => $this->translate('Disable Checks'), + 'description' => $this->translate('Whether to disable checks when this dependency fails. Defaults to false.') + ), + null + ); + + $this->addBoolean( + 'disable_notifications', + array( + 'label' => $this->translate('Disable Notificiations'), + 'description' => $this->translate('Whether to disable notifications when this dependency fails. Defaults to true.') + ), + null + ); + + $this->addBoolean( + 'ignore_soft_states', + array( + 'label' => $this->translate('Ignore Soft States'), + 'description' => $this->translate('Whether to ignore soft states for the reachability calculation. Defaults to true.') + ), + null + ); + + return $this; + } + + protected function addObjectsElement() + { + $this->addElement( + 'text', + 'parent_host', + array( + 'label' => $this->translate('Parent Host'), + 'description' => $this->translate( + 'The parent host.' + ), + 'class' => "autosubmit director-suggest", + 'data-suggestion-context' => 'hostnames', + 'order' => 10, + 'value' => $this->getObject()->get('parent_host') + + ) + ); + $sent_parent=$this->getSentOrObjectValue("parent_host"); + + if (!empty($sent_parent) || $this->object->isApplyRule()) { + $this->addElement( + 'text', + 'parent_service', + array( + 'label' => $this->translate('Parent Service'), + 'description' => $this->translate( + 'Optional. The parent service. If omitted this dependency object is treated as host dependency.' + ), + 'class' => "autosubmit director-suggest", + 'data-suggestion-context' => 'servicenames', + 'data-suggestion-for-host' => $sent_parent, + 'order' => 20, + 'value' => $this->getObject()->get('parent_service') + + ) + ); + + } + + // If configuring Object, allow selection of child host and/or service, otherwise apply rules will determine child object. + if ($this->isObject()) { + $this->addElement( + 'text', + 'child_host', + array( + 'label' => $this->translate('Child Host'), + 'description' => $this->translate( + 'The child host.' + ), + 'class' => "autosubmit director-suggest", + 'data-suggestion-context' => 'hostnames', + 'order' => 30, + 'value' => $this->getObject()->get('child_host') + ) + ); + + $sent_child=$this->getSentOrObjectValue("child_host"); + + if (!empty($sent_child)) { + $this->addElement( + 'text', + 'child_service', + array( + 'label' => $this->translate('Child Service'), + 'description' => $this->translate( + 'Optional. The child service. If omitted this dependency object is treated as host dependency.' + ), + 'class' => "autosubmit director-suggest", + 'data-suggestion-context' => 'servicenames', + 'data-suggestion-for-host' => $sent_child, + 'order' => 40, + 'value' => $this->getObject()->get('child_service') + + ) + ); + + + } + } + + $elements=array('parent_host','child_host','parent_service','child_service'); + $this->addDisplayGroup($elements, 'related_objects', array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'dl')), + 'Fieldset', + ), + 'order' => 25, + 'legend' => $this->translate('Related Objects') + )); + + + return $this; + } + + public function createApplyRuleFor(IcingaDependency $dependency) + { + $object = $this->object(); + $object->imports = $dependency->object_name; + $object->object_type = 'apply'; + $object->object_name = $dependency->object_name; + return $this; + } + + +} diff --git a/application/tables/IcingaDependencyTable.php b/application/tables/IcingaDependencyTable.php new file mode 100644 index 00000000..3c342f77 --- /dev/null +++ b/application/tables/IcingaDependencyTable.php @@ -0,0 +1,105 @@ + 'd.id', + 'object_type' => 'd.object_type', + 'dependency' => 'd.object_name', + ); + } + + protected function listTableClasses() + { + return array_merge(array('assignment-table'), parent::listTableClasses()); + } + + protected function getActionUrl($row) + { + return $this->url('director/dependency', array('id' => $row->id)); + } + + public function getTitles() + { + $view = $this->view(); + return array( + 'dependency' => $view->translate('Dependency'), + ); + } + + protected function renderRow($row) + { + $v = $this->view(); + $extra = $this->appliedOnes($row->id); + $htm = " getRowClassesString($row) . ">\n"; + $htm .= '' . $v->qlink($row->dependency, $this->getActionUrl($row)); + if (empty($extra)) { + $htm .= ' ' . $v->qlink( + 'Create apply-rule', + 'director/dependency/add', + array('apply' => $row->dependency, 'type' => 'apply'), + array('class' => 'icon-plus') + ); + + } else { + $htm .= '. Related apply rules: '; + $htm .= $v->qlink( + 'Add more', + 'director/dependency/add', + array('apply' => $row->dependency), + array('class' => 'icon-plus') + ); + } + $htm .= ''; + return $htm . " \n"; + } + + protected function appliedOnes($id) + { + $db = $this->db(); + $query = $db->select()->from( + array('s' => 'icinga_dependency'), + array( + 'id' => 's.id', + 'objectname' => 's.object_name', + ) + )->join( + array('i' => 'icinga_dependency_inheritance'), + 'i.dependency_id = s.id', + array() + )->where('i.parent_dependency_id = ?', $id) + ->where('s.object_type = ?', 'apply'); + + + return $db->fetchPairs($query); + } + + public function getUnfilteredQuery() + { + return $this->db()->select()->from( + array('d' => 'icinga_dependency'), + array() + ); + } + + public function getBaseQuery() + { + return $this->getUnfilteredQuery()->order('d.object_name'); + } +} diff --git a/application/tables/IcingaDependencyTemplateTable.php b/application/tables/IcingaDependencyTemplateTable.php new file mode 100644 index 00000000..60038b69 --- /dev/null +++ b/application/tables/IcingaDependencyTemplateTable.php @@ -0,0 +1,13 @@ +getUnfilteredQuery()->where('d.object_type = ?', 'template'); + } +} diff --git a/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php b/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php new file mode 100644 index 00000000..486e59b0 --- /dev/null +++ b/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php @@ -0,0 +1,26 @@ +translate('Dependencies.'); + } + + public function getSummary() + { + return $this->translate('Define object dependency relationships.') + . ' ' . parent::getSummary(); + } + + public function getUrl() + { + return 'director/dependencies'; + } +} diff --git a/library/Director/Dashboard/ObjectsDashboard.php b/library/Director/Dashboard/ObjectsDashboard.php index 5de0d866..0688d4ab 100644 --- a/library/Director/Dashboard/ObjectsDashboard.php +++ b/library/Director/Dashboard/ObjectsDashboard.php @@ -9,6 +9,7 @@ class ObjectsDashboard extends Dashboard 'ServiceObject', 'CommandObject', // 'Notifications', + 'DependencyObject', ); public function getTitle() diff --git a/library/Director/Db.php b/library/Director/Db.php index 1196d9f1..4c621501 100644 --- a/library/Director/Db.php +++ b/library/Director/Db.php @@ -389,6 +389,7 @@ class Db extends DbConnection 'apiuser', 'endpoint', 'zone', + 'dependency', ); $queries = array(); diff --git a/library/Director/IcingaConfig/IcingaConfig.php b/library/Director/IcingaConfig/IcingaConfig.php index 3713f0ed..e79e9707 100644 --- a/library/Director/IcingaConfig/IcingaConfig.php +++ b/library/Director/IcingaConfig/IcingaConfig.php @@ -477,6 +477,7 @@ class IcingaConfig ->createFileFromDb('userGroup') ->createFileFromDb('user') ->createFileFromDb('notification') + ->createFileFromDb('dependency') ; if (! $this->isLegacy()) { @@ -719,7 +720,8 @@ apply Service for (title => params in host.vars["%s"]) { 'user', 'userGroup', 'timePeriod', - 'notification' + 'notification', + 'dependency' ); return in_array($type, $types); diff --git a/library/Director/Objects/IcingaDependency.php b/library/Director/Objects/IcingaDependency.php new file mode 100644 index 00000000..43b363c9 --- /dev/null +++ b/library/Director/Objects/IcingaDependency.php @@ -0,0 +1,421 @@ + null, + 'object_name' => null, + 'object_type' => null, + 'disabled' => 'n', + 'apply_to' => null, + 'parent_host_id' => null, + 'parent_service_id' => null, + 'child_host_id' => null, + 'child_service_id' => null, + 'disable_checks' => null, + 'disable_notifications' => null, + 'ignore_soft_states' => null, + 'period_id' => null, + 'zone_id' => null, + 'assign_filter' => null, + 'parent_service_s' => null, + ); + + protected $supportsCustomVars = false; + + protected $supportsImports = true; + + protected $supportsApplyRules = true; + + protected $relatedSets = array( + 'states' => 'StateFilterSet', + ); + + protected $relations = array( + 'zone' => 'IcingaZone', + 'parent_host' => 'IcingaHost', + 'parent_service' => 'IcingaService', + 'child_host' => 'IcingaHost', + 'child_service' => 'IcingaService', + 'period' => 'IcingaTimePeriod', + ); + + protected $booleans = array( + 'disable_checks' => 'disable_checks', + 'disable_notifications' => 'disable_notifications', + 'ignore_soft_states' => 'ignore_soft_states' + ); + + /** + * Do not render internal property apply_to + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderApply_to() + { + // @codingStandardsIgnoreEnd + return ''; + } + + protected function renderObjectHeader() + { + if ($this->isApplyRule()) { + if (($to = $this->get('apply_to')) === null) { + throw new ConfigurationError( + 'Applied dependency "%s" has no valid object type', + $this->getObjectName() + ); + } + + return sprintf( + "%s %s %s to %s {\n", + $this->getObjectTypeName(), + $this->getType(), + c::renderString($this->getObjectName()), + ucfirst($to) + ); + + } else { + return parent::renderObjectHeader(); + } + } + + protected function setKey($key) + { + if (is_int($key)) { + $this->id = $key; + } elseif (is_array($key)) { + foreach (array('id', 'parent_host_id', 'parent_service_id', 'child_host_id', 'child_service_id', 'object_name') as $k) { + if (array_key_exists($k, $key)) { + $this->set($k, $key[$k]); + } + } + } else { + return parent::setKey($key); + } + + return $this; + } + + protected function renderAssignments() + { + if ($this->hasBeenAssignedToServiceApply()) { + + $tmpService= $this->getRelatedObject('child_service', $this->child_service_id); + $assigns = $tmpService->assignments()->toConfigString(); + + $filter = sprintf( + '%s && service.name == "%s"', + trim($assigns), $this->child_service + ); + return "\n " . $filter . "\n"; + } + + if ($this->hasBeenAssignedToHostTemplateService()) { + $filter = sprintf( + 'assign where "%s" in host.templates && service.name == "%s"', + $this->child_host, $this->child_service + ); + return "\n " . $filter . "\n"; + } + if ($this->hasBeenAssignedToHostTemplate()) { + $filter = sprintf( + 'assign where "%s" in host.templates', + $this->child_host + ); + return "\n " . $filter . "\n"; + } + + if ($this->hasBeenAssignedToServiceTemplate()) { + $filter = sprintf( + 'assign where "%s" in service.templates', + $this->child_service + ); + return "\n " . $filter . "\n"; + } + + return parent::renderAssignments(); + } + + protected function hasBeenAssignedToHostTemplate() + { + try { + return $this->child_host_id && $this->getRelatedObject( + 'child_host', + $this->child_host_id + )->object_type === 'template'; + } catch (NotFoundError $e) { + return false; + } + } + + protected function hasBeenAssignedToServiceTemplate() + { + try { + return $this->child_service_id && $this->getRelatedObject( + 'child_service', + $this->child_service_id + )->object_type === 'template'; + } catch (NotFoundError $e) { + return false; + } + } + + protected function hasBeenAssignedToHostTemplateService() + { + if (!$this->hasBeenAssignedToHostTemplate()) return false; + try { + return $this->child_service_id && $this->getRelatedObject( + 'child_service', + $this->child_service_id + )->object_type === 'object'; + } catch (NotFoundError $e) { + return false; + } + } + + protected function hasBeenAssignedToServiceApply() + { + try { + return $this->child_service_id && $this->getRelatedObject( + 'child_service', + $this->child_service_id + )->object_type === 'apply'; + } catch (NotFoundError $e) { + return false; + } + } + + + /** + * Render child_host_id as host_name + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderChild_host_id() + { + // @codingStandardsIgnoreEnd + + if ($this->hasBeenAssignedToHostTemplate()) { + return ''; + } + + return $this->renderRelationProperty('child_host', $this->child_host_id, 'child_host_name'); + } + + /** + * Render parent_host_id as parent_host_name + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderParent_host_id() + { + // @codingStandardsIgnoreEnd + + return $this->renderRelationProperty('parent_host', $this->parent_host_id, 'parent_host_name'); + } + + + /** + * Render child_service_id as host_name + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderChild_service_id() + { + // @codingStandardsIgnoreEnd + if ($this->hasBeenAssignedToServiceTemplate()) { + return ''; + } + + if ($this->hasBeenAssignedToHostTemplateService()) { + return ''; + } + + if ($this->hasBeenAssignedToServiceApply()) { + return ''; + } + + return $this->renderRelationProperty('child_service', $this->child_service_id, 'child_service_name'); + } + + /** + * Render parent_service_id as parent_service_name + * + * Avoid complaints for method names with underscore: + * @codingStandardsIgnoreStart + * + * @return string + */ + public function renderParent_service_id() + { + return $this->renderRelationProperty('parent_service', $this->parent_service_id, 'parent_service_name'); + } + + //special case for parent service set as plain string for Apply rules + public function renderParent_service_s() + { + return "\n parent_service_name = \"" . $this->parent_service_s ."\"\n"; + } + + public function isApplyRule() + { + if ($this->hasBeenAssignedToHostTemplate()) { + return true; + } + + if ($this->hasBeenAssignedToServiceTemplate()) { + return true; + } + + if ($this->hasBeenAssignedToServiceApply()) { + return true; + } + + return $this->hasProperty('object_type') + && $this->object_type === 'apply'; + } + + protected function resolveUnresolvedRelatedProperty($name) + { + + $short = substr($name, 0, -3); + /** @var IcingaObject $class */ + $class = $this->getRelationClass($short); + $obj_key = $this->unresolvedRelatedProperties[$name]; + + # related services need array key + if ($class == "Icinga\Module\Director\Objects\IcingaService" ) { + if ($name == "parent_service_id" && $this->object_type == 'apply' ) { //special case , parent service can be set as simple string for Apply + if ($this->properties['parent_host_id']==null) { + $this->reallySet('parent_service_s', $this->unresolvedRelatedProperties[$name]); + $this->reallySet('parent_service_id',null); + unset($this->unresolvedRelatedProperties[$name]); + return; + } + } + + $this->reallySet('parent_service_s',null); + $host_id_prop=str_replace("service","host",$name); + if (isset($this->properties[$host_id_prop])) { + $obj_key=array("host_id" => $this->properties[$host_id_prop], "object_name" => $this->unresolvedRelatedProperties[$name]); + } else { + $obj_key=array("host_id" => null, "object_name" => $this->unresolvedRelatedProperties[$name]); + } + + try { + $object = $class::load( $obj_key, $this->connection); + } catch (NotFoundError $e) { + // Not a simple service on host + // Hunt through inherited services, use service assigned to template if found + $tmp_host=IcingaHost::loadWithAutoIncId($this->properties[$host_id_prop], $this->connection); + + //services for applicable templates + $resolver = $tmp_host->templateResolver(); + foreach ($resolver->fetchResolvedParents() as $template_obj) { + $obj_key=array("host_id" => $template_obj->id, "object_name" => $this->unresolvedRelatedProperties[$name]); + try { + $object = $class::load( $obj_key, $this->connection); + } catch (NotFoundError $e) { + continue; + } + break; + } + if (!isset($object)) { //Not an inherited service, now try apply rules + $matcher = HostApplyMatches::prepare($tmp_host); + foreach ($this->getAllApplyRules() as $rule) { + if ($matcher->matchesFilter($rule->filter)) { + if ($rule->name == $this->unresolvedRelatedProperties[$name]) { + $object=IcingaService::loadWithAutoIncId($rule->id, $this->connection); + break; + } + } + } + } + } + } else { + $object = $class::load( + $obj_key, + $this->connection + ); + } + + if (isset($object)) { + $this->reallySet($name, $object->get('id')); + unset($this->unresolvedRelatedProperties[$name]); + } else { + throw new NotFoundError('Unable to resolve related property: "%s"', $name); + } + } + + protected function getAllApplyRules() + { + $allApplyRules=$this->fetchAllApplyRules(); + foreach ($allApplyRules as $rule) { + $rule->filter = Filter::fromQueryString($rule->assign_filter); + } + + return $allApplyRules; + } + + protected function fetchAllApplyRules() + { + $db = $this->connection->getDbAdapter(); + $query = $db->select()->from( + array('s' => 'icinga_service'), + array( + 'id' => 's.id', + 'name' => 's.object_name', + 'assign_filter' => 's.assign_filter', + ) + )->where('object_type = ? AND assign_filter IS NOT NULL', 'apply'); + + return $db->fetchAll($query); + } + + protected function getRelatedProperty($key) + { + $idKey = $key . '_id'; + if ($this->hasUnresolvedRelatedProperty($idKey)) { + return $this->unresolvedRelatedProperties[$idKey]; + } + + if ($id = $this->get($idKey)) { + /** @var IcingaObject $class */ + $class = $this->getRelationClass($key); + $object = $class::loadWithAutoIncId($id, $this->connection); + return $object->get('object_name'); + } else { + // handle special case for plain string parent service on Dependency Apply rules + if ($key == 'parent_service' && $this->get('parent_service_s') != null) { + return $this->get('parent_service_s'); + } + } + + return null; + } + +} diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index 9f756046..4e509ea7 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -2699,7 +2699,8 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer public function getOnDeleteUrl() { - return 'director/' . strtolower($this->getShortTableName()) . 's'; + $plural= preg_replace('/cys$/','cies', strtolower($this->getShortTableName()) . 's'); + return 'director/' . $plural; } public function toJson( diff --git a/library/Director/Web/ActionBar/TemplateActionBar.php b/library/Director/Web/ActionBar/TemplateActionBar.php index e6d7f766..c3b28a1c 100644 --- a/library/Director/Web/ActionBar/TemplateActionBar.php +++ b/library/Director/Web/ActionBar/TemplateActionBar.php @@ -9,6 +9,7 @@ class TemplateActionBar extends DirectorBaseActionBar protected function assemble() { $type = $this->type; + $pltype = preg_replace('/cys$/','cies', $type . 's'); $renderTree = $this->url->getParam('render') === 'tree'; $renderParams = $renderTree ? null : ['render' => 'tree']; $this->add( @@ -27,7 +28,7 @@ class TemplateActionBar extends DirectorBaseActionBar )->add( Link::create( $renderTree ? $this->translate('Table') : $this->translate('Tree'), - "director/${type}s/templates", + "director/$pltype/templates", $renderParams, [ 'class' => 'icon-' . ($renderTree ? 'doc-text' : 'sitemap'), diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php index 98c39b01..7a1499e6 100644 --- a/library/Director/Web/Controller/ObjectsController.php +++ b/library/Director/Web/Controller/ObjectsController.php @@ -80,7 +80,7 @@ abstract class ObjectsController extends ActionController $this ->addObjectsTabs() ->setAutorefreshInterval(10) - ->addTitle($this->translate(ucfirst(strtolower($type)) . 's')) + ->addTitle($this->translate(ucfirst($this->getPluralType()))) ->actions(new ObjectsActionBar($type, $this->url())); if ($type === 'command' && $this->params->get('type') === 'external_object') { @@ -127,6 +127,7 @@ abstract class ObjectsController extends ActionController )->content()->add($form); } + /** * Loads the TemplatesTable or the TemplateTreeRenderer * @@ -139,7 +140,6 @@ abstract class ObjectsController extends ActionController return; } $type = $this->getType(); - $shortType = IcingaObject::createByType($type)->getShortTableName(); $this ->assertPermission('director/admin') @@ -294,8 +294,8 @@ abstract class ObjectsController extends ActionController { // Strip final 's' and upcase an eventual 'group' return preg_replace( - array('/group$/', '/period$/', '/argument$/', '/apiuser$/'), - array('Group', 'Period', 'Argument', 'ApiUser'), + array('/group$/', '/period$/', '/argument$/', '/apiuser$/', '/dependencie$/'), + array('Group', 'Period', 'Argument', 'ApiUser', 'dependency'), str_replace( 'template', '', @@ -306,11 +306,11 @@ abstract class ObjectsController extends ActionController protected function getPluralType() { - return $this->getType() . 's'; + return preg_replace("/cys$/","cies",$this->getType() . 's'); } protected function getPluralBaseType() { - return $this->getBaseType() . 's'; + return preg_replace("/cys$/","cies",$this->getBaseType() . 's'); } } diff --git a/library/Director/Web/Controller/TemplateController.php b/library/Director/Web/Controller/TemplateController.php index 98607792..4ee47702 100644 --- a/library/Director/Web/Controller/TemplateController.php +++ b/library/Director/Web/Controller/TemplateController.php @@ -177,7 +177,7 @@ abstract class TemplateController extends CompatController protected function getPluralType() { - return $this->template()->getShortTableName() . 's'; + return preg_replace('/cys$/','cies',$this->template()->getShortTableName() . 's'); } protected function getTranslatedType() diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php index 3461624b..72a63d0a 100644 --- a/library/Director/Web/Form/DirectorObjectForm.php +++ b/library/Director/Web/Form/DirectorObjectForm.php @@ -523,6 +523,9 @@ abstract class DirectorObjectForm extends DirectorForm 'email', 'pager', 'enable_notifications', + 'disable_checks', //Dependencies + 'disable_notifications', + 'ignore_soft_states', 'apply_for', 'create_live', 'disabled', @@ -1508,28 +1511,28 @@ abstract class DirectorObjectForm extends DirectorForm return $this; } - protected function addEventFilterElements() + protected function addEventFilterElements($elements = array('states','types')) { - $this->addElement('extensibleSet', 'states', array( - 'label' => $this->translate('States'), - 'multiOptions' => $this->optionallyAddFromEnum($this->enumStates()), - 'description' => $this->translate( - 'The host/service states you want to get notifications for' - ), - )); + if (in_array('states', $elements)) { + $this->addElement('extensibleSet', 'states', array( + 'label' => $this->translate('States'), + 'multiOptions' => $this->optionallyAddFromEnum($this->enumStates()), + 'description' => $this->translate( + 'The host/service states you want to get notifications for' + ), + )); + } - $this->addElement('extensibleSet', 'types', array( - 'label' => $this->translate('Transition types'), - 'multiOptions' => $this->optionallyAddFromEnum($this->enumTypes()), - 'description' => $this->translate( - 'The state transition types you want to get notifications for' - ), - )); + if (in_array('types', $elements)) { + $this->addElement('extensibleSet', 'types', array( + 'label' => $this->translate('Transition types'), + 'multiOptions' => $this->optionallyAddFromEnum($this->enumTypes()), + 'description' => $this->translate( + 'The state transition types you want to get notifications for' + ), + )); + } - $elements = array( - 'states', - 'types', - ); $this->addDisplayGroup($elements, 'event_filters', array( 'decorators' => array( 'FormElements', diff --git a/library/Director/Web/Tabs/ObjectsTabs.php b/library/Director/Web/Tabs/ObjectsTabs.php index adef4a54..83037ae8 100644 --- a/library/Director/Web/Tabs/ObjectsTabs.php +++ b/library/Director/Web/Tabs/ObjectsTabs.php @@ -18,17 +18,17 @@ class ObjectsTabs extends Tabs $object = IcingaObject::createByType(substr($type, 0, -5)); } - // TODO: plural? - if ($auth->hasPermission("director/${type}s")) { + $pltype=strtolower(preg_replace('/cys$/','cies',$type . 's')); + if ($auth->hasPermission("director/${pltype}")) { $this->add('index', array( - 'url' => sprintf('director/%ss', strtolower($type)), - 'label' => $this->translate(ucfirst($type) . 's'), + 'url' => sprintf('director/%s', $pltype), + 'label' => $this->translate(ucfirst($pltype)), )); } if ($object->getShortTableName() === 'command') { $this->add('external', array( - 'url' => sprintf('director/%ss', strtolower($type)), + 'url' => sprintf('director/%s', strtolower($pltype)), 'urlParams' => ['type' => 'external_object'], 'label' => $this->translate('External'), )); @@ -39,7 +39,7 @@ class ObjectsTabs extends Tabs )) { if ($object->supportsApplyRules()) { $this->add('applyrules', array( - 'url' => sprintf('director/%ss/applyrules', $type), + 'url' => sprintf('director/%s/applyrules', $pltype), 'label' => $this->translate('Apply') )); } @@ -48,7 +48,7 @@ class ObjectsTabs extends Tabs if ($auth->hasPermission('director/admin') && $type !== 'zone') { if ($object->supportsImports()) { $this->add('templates', array( - 'url' => sprintf('director/%ss/templates', strtolower($type)), + 'url' => sprintf('director/%s/templates', $pltype), 'label' => $this->translate('Templates'), )); } @@ -71,7 +71,7 @@ class ObjectsTabs extends Tabs } if ($object->supportsSets() && $auth->hasPermission("director/${type}_sets")) { $this->add('sets', array( - 'url' => sprintf('director/%ss/sets', $type), + 'url' => sprintf('director/%s/sets', $pltype), 'label' => $this->translate('Sets') )); } diff --git a/public/js/module.js b/public/js/module.js index 2057e88f..c8093778 100644 --- a/public/js/module.js +++ b/public/js/module.js @@ -275,7 +275,8 @@ { $suggestions.load(this.module.icinga.config.baseUrl + '/director/suggest', { value: $el.val(), - context: $el.data('suggestion-context') + context: $el.data('suggestion-context'), + for_host: $el.data('suggestion-for-host') }, function (responseText, textStatus, jqXHR) { var $li = $suggestions.find('li'); if ($li.length) { diff --git a/schema/mysql-migrations/upgrade_dependencies.sql b/schema/mysql-migrations/upgrade_dependencies.sql new file mode 100644 index 00000000..d80098b1 --- /dev/null +++ b/schema/mysql-migrations/upgrade_dependencies.sql @@ -0,0 +1,88 @@ + +CREATE TABLE icinga_dependency ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + object_name VARCHAR(255) DEFAULT NULL, + object_type ENUM('object', 'template', 'apply') NOT NULL, + disabled ENUM('y', 'n') NOT NULL DEFAULT 'n', + apply_to ENUM('host', 'service') DEFAULT NULL, + parent_host_id INT(10) UNSIGNED DEFAULT NULL, + parent_service_id INT(10) UNSIGNED DEFAULT NULL, + child_host_id INT(10) UNSIGNED DEFAULT NULL, + child_service_id INT(10) UNSIGNED DEFAULT NULL, + disable_checks ENUM('y', 'n'), + disable_notifications ENUM('y', 'n'), + ignore_soft_states ENUM('y', 'n'), + period_id INT(10) UNSIGNED DEFAULT NULL, + zone_id INT(10) UNSIGNED DEFAULT NULL, + assign_filter TEXT DEFAULT NULL, + PRIMARY KEY (id), + CONSTRAINT icinga_dependency_parent_host + FOREIGN KEY parent_host (parent_host_id) + REFERENCES icinga_host (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_parent_service + FOREIGN KEY parent_service (parent_service_id) + REFERENCES icinga_service (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_child_host + FOREIGN KEY child_host (child_host_id) + REFERENCES icinga_host (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_child_service + FOREIGN KEY child_service (child_service_id) + REFERENCES icinga_service (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_period + FOREIGN KEY period (period_id) + REFERENCES icinga_timeperiod (id) + ON DELETE RESTRICT + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_zone + FOREIGN KEY zone (zone_id) + REFERENCES icinga_zone (id) + ON DELETE RESTRICT + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE icinga_dependency_inheritance ( + dependency_id INT(10) UNSIGNED NOT NULL, + parent_dependency_id INT(10) UNSIGNED NOT NULL, + weight MEDIUMINT UNSIGNED DEFAULT NULL, + PRIMARY KEY (dependency_id, parent_dependency_id), + UNIQUE KEY unique_order (dependency_id, weight), + CONSTRAINT icinga_dependency_inheritance_dependency + FOREIGN KEY dependency (dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_inheritance_parent_dependency + FOREIGN KEY parent_dependency (parent_dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE RESTRICT + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE icinga_dependency_states_set ( + dependency_id INT(10) UNSIGNED NOT NULL, + property ENUM( + 'OK', + 'Warning', + 'Critical', + 'Unknown', + 'Up', + 'Down' + ) NOT NULL, + merge_behaviour ENUM('override', 'extend', 'blacklist') NOT NULL DEFAULT 'override' + COMMENT 'override: = [], extend: += [], blacklist: -= []', + PRIMARY KEY (dependency_id, property, merge_behaviour), + CONSTRAINT icinga_dependency_states_set_dependency + FOREIGN KEY icinga_dependency (dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE CASCADE + ON UPDATE CASCADE +) ENGINE=InnoDB; + diff --git a/schema/mysql-migrations/upgrade_dependencies_2.sql b/schema/mysql-migrations/upgrade_dependencies_2.sql new file mode 100644 index 00000000..34f2e5dc --- /dev/null +++ b/schema/mysql-migrations/upgrade_dependencies_2.sql @@ -0,0 +1,2 @@ + +ALTER TABLE icinga_dependency ADD COLUMN parent_service_s VARCHAR(255); diff --git a/schema/mysql.sql b/schema/mysql.sql index 5f0887db..b0defba1 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -1575,6 +1575,94 @@ CREATE TABLE icinga_user_resolved_var ( ON UPDATE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE icinga_dependency ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + object_name VARCHAR(255) DEFAULT NULL, + object_type ENUM('object', 'template', 'apply') NOT NULL, + disabled ENUM('y', 'n') NOT NULL DEFAULT 'n', + apply_to ENUM('host', 'service') DEFAULT NULL, + parent_host_id INT(10) UNSIGNED DEFAULT NULL, + parent_service_id INT(10) UNSIGNED DEFAULT NULL, + child_host_id INT(10) UNSIGNED DEFAULT NULL, + child_service_id INT(10) UNSIGNED DEFAULT NULL, + disable_checks ENUM('y', 'n'), + disable_notifications ENUM('y', 'n'), + ignore_soft_states ENUM('y', 'n'), + period_id INT(10) UNSIGNED DEFAULT NULL, + zone_id INT(10) UNSIGNED DEFAULT NULL, + assign_filter TEXT DEFAULT NULL, + parent_service_s VARCHAR(255) DEFAULT NULL, + PRIMARY KEY (id), + CONSTRAINT icinga_dependency_parent_host + FOREIGN KEY parent_host (parent_host_id) + REFERENCES icinga_host (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_parent_service + FOREIGN KEY parent_service (parent_service_id) + REFERENCES icinga_service (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_child_host + FOREIGN KEY child_host (child_host_id) + REFERENCES icinga_host (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_child_service + FOREIGN KEY child_service (child_service_id) + REFERENCES icinga_service (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_period + FOREIGN KEY period (period_id) + REFERENCES icinga_timeperiod (id) + ON DELETE RESTRICT + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_zone + FOREIGN KEY zone (zone_id) + REFERENCES icinga_zone (id) + ON DELETE RESTRICT + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE icinga_dependency_inheritance ( + dependency_id INT(10) UNSIGNED NOT NULL, + parent_dependency_id INT(10) UNSIGNED NOT NULL, + weight MEDIUMINT UNSIGNED DEFAULT NULL, + PRIMARY KEY (dependency_id, parent_dependency_id), + UNIQUE KEY unique_order (dependency_id, weight), + CONSTRAINT icinga_dependency_inheritance_dependency + FOREIGN KEY dependency (dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT icinga_dependency_inheritance_parent_dependency + FOREIGN KEY parent_dependency (parent_dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE RESTRICT + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE icinga_dependency_states_set ( + dependency_id INT(10) UNSIGNED NOT NULL, + property ENUM( + 'OK', + 'Warning', + 'Critical', + 'Unknown', + 'Up', + 'Down' + ) NOT NULL, + merge_behaviour ENUM('override', 'extend', 'blacklist') NOT NULL DEFAULT 'override' + COMMENT 'override: = [], extend: += [], blacklist: -= []', + PRIMARY KEY (dependency_id, property, merge_behaviour), + CONSTRAINT icinga_dependency_states_set_dependency + FOREIGN KEY icinga_dependency (dependency_id) + REFERENCES icinga_dependency (id) + ON DELETE CASCADE + ON UPDATE CASCADE +) ENGINE=InnoDB; + INSERT INTO director_schema_migration (schema_version, migration_time) VALUES (143, NOW());