diff --git a/library/Icingadb/Model/Dependency.php b/library/Icingadb/Model/Dependency.php new file mode 100644 index 000000000..0a32a8096 --- /dev/null +++ b/library/Icingadb/Model/Dependency.php @@ -0,0 +1,95 @@ +add(new Binary([ + 'id', + 'redundancy_group_id', + 'timeperiod_id' + ])); + $behaviors->add(new BoolCast([ + 'disable_checks', + 'disable_notifications', + 'ignore_soft_states' + ])); + $behaviors->add(new Bitmask([ + 'states' => [ + 'ok' => 1, + 'warning' => 2, + 'critical' => 4, + 'unknown' => 8, + 'up' => 16, + 'down' => 32 + ], + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('timeperiod', Timeperiod::class) + ->setJoinType('LEFT'); + $relations->belongsTo('redundancy_group', RedundancyGroup::class) + ->setJoinType('LEFT'); + + $relations->hasOne('state', DependencyState::class) + ->setJoinType('LEFT'); + + $relations->hasOne('edge', DependencyEdge::class); + } +} diff --git a/library/Icingadb/Model/DependencyEdge.php b/library/Icingadb/Model/DependencyEdge.php new file mode 100644 index 000000000..d6c6599c2 --- /dev/null +++ b/library/Icingadb/Model/DependencyEdge.php @@ -0,0 +1,63 @@ +add(new Binary([ + 'to_node_id', + 'from_node_id', + 'dependency_id' + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('from', DependencyNode::class) + ->setCandidateKey('from_node_id'); + $relations->belongsTo('to', DependencyNode::class) + ->setCandidateKey('to_node_id'); + $relations->belongsTo('dependency', Dependency::class) + ->setJoinType('LEFT'); + } +} diff --git a/library/Icingadb/Model/DependencyNode.php b/library/Icingadb/Model/DependencyNode.php new file mode 100644 index 000000000..995e9be7e --- /dev/null +++ b/library/Icingadb/Model/DependencyNode.php @@ -0,0 +1,88 @@ +add(new Binary([ + 'id', + 'host_id', + 'service_id', + 'redundancy_group_id' + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('host', Host::class) + ->setJoinType('LEFT'); + $relations->belongsTo('service', Service::class) + ->setJoinType('LEFT'); + $relations->belongsTo('redundancy_group', RedundancyGroup::class) + ->setJoinType('LEFT'); + + $relations->hasMany('from', DependencyEdge::class) + ->setForeignKey('from_node_id') + ->setJoinType('LEFT'); + $relations->hasMany('to', DependencyEdge::class) + ->setForeignKey('to_node_id') + ->setJoinType('LEFT'); + + $relations->belongsToMany('child', self::class) + ->through(DependencyEdge::class) + ->setForeignKey('to_node_id') + ->setTargetForeignKey('from_node_id') + ->setJoinType('LEFT'); + $relations->belongsToMany('parent', self::class) + ->through(DependencyEdge::class) + ->setForeignKey('from_node_id') + ->setTargetForeignKey('to_node_id') + ->setJoinType('LEFT'); + } +} diff --git a/library/Icingadb/Model/DependencyState.php b/library/Icingadb/Model/DependencyState.php new file mode 100644 index 000000000..cf1eb5de0 --- /dev/null +++ b/library/Icingadb/Model/DependencyState.php @@ -0,0 +1,58 @@ +add(new Binary([ + 'id', + 'dependency_id' + ])); + $behaviors->add(new BoolCast([ + 'failed' + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('dependency', Dependency::class); + } +} diff --git a/library/Icingadb/Model/Host.php b/library/Icingadb/Model/Host.php index c6979839e..a68275950 100644 --- a/library/Icingadb/Model/Host.php +++ b/library/Icingadb/Model/Host.php @@ -181,6 +181,8 @@ public function createBehaviors(Behaviors $behaviors) ])); $behaviors->add(new ReRoute([ + 'child' => 'dependency_node.child', + 'parent' => 'dependency_node.parent', 'servicegroup' => 'service.servicegroup', 'user' => 'notification.user', 'usergroup' => 'notification.usergroup' @@ -235,6 +237,8 @@ public function createDefaults(Defaults $defaults) public function createRelations(Relations $relations) { + $relations->belongsTo('dependency_node', DependencyNode::class) + ->setJoinType('LEFT'); $relations->belongsTo('environment', Environment::class); $relations->belongsTo('eventcommand', Eventcommand::class); $relations->belongsTo('checkcommand', Checkcommand::class); diff --git a/library/Icingadb/Model/RedundancyGroup.php b/library/Icingadb/Model/RedundancyGroup.php new file mode 100644 index 000000000..c8a76e7bf --- /dev/null +++ b/library/Icingadb/Model/RedundancyGroup.php @@ -0,0 +1,67 @@ +add(new Binary([ + 'id' + ])); + $behaviors->add(new ReRoute([ + 'child' => 'dependency_node.child', + 'parent' => 'dependency_node.parent' + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('dependency_node', DependencyNode::class) + ->setForeignKey('redundancy_group_id') + ->setCandidateKey('id'); + + $relations->hasOne('state', RedundancyGroupState::class) + ->setJoinType('LEFT'); + + $relations->hasMany('dependency', Dependency::class); + } +} diff --git a/library/Icingadb/Model/RedundancyGroupState.php b/library/Icingadb/Model/RedundancyGroupState.php new file mode 100644 index 000000000..1d8da858f --- /dev/null +++ b/library/Icingadb/Model/RedundancyGroupState.php @@ -0,0 +1,58 @@ +add(new Binary([ + 'id', + 'redundancy_group_id' + ])); + $behaviors->add(new BoolCast([ + 'failed' + ])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('redundancy_group', RedundancyGroup::class); + } +} diff --git a/library/Icingadb/Model/Service.php b/library/Icingadb/Model/Service.php index 1d97b96ac..1383823ab 100644 --- a/library/Icingadb/Model/Service.php +++ b/library/Icingadb/Model/Service.php @@ -170,6 +170,8 @@ public function createBehaviors(Behaviors $behaviors) ])); $behaviors->add(new ReRoute([ + 'child' => 'dependency_node.child', + 'parent' => 'dependency_node.parent', 'user' => 'notification.user', 'usergroup' => 'notification.usergroup' ])); @@ -222,6 +224,8 @@ public function createDefaults(Defaults $defaults) public function createRelations(Relations $relations) { + $relations->belongsTo('dependency_node', DependencyNode::class) + ->setJoinType('LEFT'); $relations->belongsTo('environment', Environment::class); $relations->belongsTo('host', Host::class)->setJoinType('LEFT'); $relations->belongsTo('checkcommand', Checkcommand::class); diff --git a/library/Icingadb/Model/Timeperiod.php b/library/Icingadb/Model/Timeperiod.php index e1664103b..34ff55096 100644 --- a/library/Icingadb/Model/Timeperiod.php +++ b/library/Icingadb/Model/Timeperiod.php @@ -98,5 +98,6 @@ public function createRelations(Relations $relations) $relations->hasMany('service', Service::class) ->setForeignKey('check_timeperiod_id'); $relations->hasMany('user', User::class); + $relations->hasMany('dependency', Dependency::class); } } diff --git a/library/Icingadb/Model/UnreachableParent.php b/library/Icingadb/Model/UnreachableParent.php new file mode 100644 index 000000000..de3608a3c --- /dev/null +++ b/library/Icingadb/Model/UnreachableParent.php @@ -0,0 +1,181 @@ +belongsTo('host', Host::class) + ->setJoinType('LEFT'); + $relations->belongsTo('service', Service::class) + ->setJoinType('LEFT'); + $relations->belongsTo('redundancy_group', RedundancyGroup::class) + ->setJoinType('LEFT'); + $relations->belongsTo('dependency', Dependency::class) + ->setJoinType('LEFT'); + } + + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new Binary([ + 'id', + 'host_id', + 'service_id', + 'redundancy_group_id', + 'dependency_id' + ])); + + $behaviors->add(new ReRoute([ + 'hostgroup' => 'host.hostgroup', + 'servicegroup' => 'service.servicegroup' + ])); + } + + public static function on(Connection $db, Model $root = null): Query + { + if ($root === null) { + throw new InvalidArgumentException('Root node must not be null'); + } + + $query = parent::on($db); + $query->getSelectBase()->with( + self::selectNodes($db, $root), + 'unreachable_parent', + true + )->where([ + 'unreachable_parent.level > ?' => 0, + 'unreachable_parent.is_group_member = ?' => 0 + ]); + + return $query; + } + + private static function selectNodes(Connection $db, Model $root): Select + { + $rootQuery = DependencyNode::on($db) + ->columns([ + 'id' => 'id', + 'level' => new Expression('0'), + 'host_id' => 'host_id', + 'service_id' => new Expression("COALESCE(%s, CAST('' as binary(20)))", ['service_id']), + 'redundancy_group_id' => new Expression("CAST('' as binary(20))"), + 'dependency_id' => new Expression("CAST('' as binary(20))"), + 'is_group_member' => new Expression('0') + ]); + if ($root instanceof Host) { + $rootQuery->filter(Filter::all( + Filter::equal('host_id', $root->id), + Filter::unlike('service_id', '*') + )); + } elseif ($root instanceof Service) { + $rootQuery->filter(Filter::all( + Filter::equal('host_id', $root->host_id), + Filter::equal('service_id', $root->id) + )); + } else { + throw new InvalidArgumentException('Root node must be either a host or a service'); + } + + $nodeQuery = DependencyEdge::on($db) + ->columns([ + 'id' => 'to_node_id', + 'level' => new Expression('urn.level + 1'), + 'host_id' => 'to.host_id', + 'service_id' => 'to.service_id', + 'redundancy_group_id' => 'to.redundancy_group_id', + 'dependency_id' => 'dependency_id', + 'is_group_member' => new Expression('urn.redundancy_group_id IS NOT NULL AND urn.level > 0') + ]); + $nodeQuery->filter(Filter::any( + Filter::equal('dependency.state.failed', 'y'), + Filter::equal('to.redundancy_group.state.failed', 'y') + )); + + $nodeSelect = $nodeQuery->assembleSelect(); + + // TODO: ipl-orm doesn't preserve key order :'( + $columnsProperty = (new \ReflectionClass($nodeSelect))->getProperty('columns'); + $columnsProperty->setAccessible(true); + $columnsProperty->setValue($nodeSelect, array_merge( + [ + 'id' => null, + 'level' => null, + 'host_id' => null, + 'service_id' => null, + 'redundancy_group_id' => null, + 'dependency_id' => null, + 'is_group_member' => null + ], + $nodeSelect->getColumns() + )); + + return $rootQuery->assembleSelect()->union( + $nodeSelect + ->join(['urn' => 'unreachable_parent'], sprintf( + 'urn.id = %s', + $nodeQuery + ->getResolver() + ->qualifyColumn( + 'from_node_id', + $nodeQuery + ->getResolver() + ->getAlias($nodeQuery->getModel()) + ) + )) + ); + } +}