From 22d53cf3b54c29b0be530dfeda4937dff2a6fc1d Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 27 Feb 2014 11:05:55 +0100 Subject: [PATCH] Implement a new object type for service dependencies. Fixes #2799 --- components/checker/checkercomponent.cpp | 5 + components/compat/statusdatawriter.cpp | 42 +++-- doc/4.3-object-types.md | 56 +++++- lib/icinga/CMakeLists.txt | 5 +- lib/icinga/dependency.cpp | 137 ++++++++++++++ lib/icinga/dependency.h | 57 ++++++ lib/icinga/dependency.ti | 25 +++ lib/icinga/host.cpp | 133 +++++-------- lib/icinga/host.h | 12 +- lib/icinga/host.ti | 3 +- lib/icinga/icinga-type.conf | 48 +++-- lib/icinga/service-check.cpp | 3 +- lib/icinga/service-dependency.cpp | 240 ++++++++++++++++++++++++ lib/icinga/service.cpp | 101 +--------- lib/icinga/service.h | 20 +- lib/icinga/service.ti | 3 +- 16 files changed, 645 insertions(+), 245 deletions(-) create mode 100644 lib/icinga/dependency.cpp create mode 100644 lib/icinga/dependency.h create mode 100644 lib/icinga/dependency.ti create mode 100644 lib/icinga/service-dependency.cpp diff --git a/components/checker/checkercomponent.cpp b/components/checker/checkercomponent.cpp index 4e59d3535..f0ec43f55 100644 --- a/components/checker/checkercomponent.cpp +++ b/components/checker/checkercomponent.cpp @@ -132,6 +132,11 @@ void CheckerComponent::CheckThreadProc(void) bool check = true; if (!forced) { + if (!service->IsReachable(DependencyCheckExecution)) { + Log(LogDebug, "icinga", "Skipping check for service '" + service->GetName() + "': Dependency failed."); + check = false; + } + if (!service->GetEnableActiveChecks() || !IcingaApplication::GetInstance()->GetEnableChecks()) { Log(LogDebug, "checker", "Skipping check for service '" + service->GetName() + "': active checks are disabled"); check = false; diff --git a/components/compat/statusdatawriter.cpp b/components/compat/statusdatawriter.cpp index 31397832c..8c2ba0105 100644 --- a/components/compat/statusdatawriter.cpp +++ b/components/compat/statusdatawriter.cpp @@ -27,6 +27,7 @@ #include "icinga/timeperiod.h" #include "icinga/notificationcommand.h" #include "icinga/compatutility.h" +#include "icinga/dependency.h" #include "base/dynamictype.h" #include "base/objectlock.h" #include "base/convert.h" @@ -508,25 +509,6 @@ void StatusDataWriter::DumpServiceObject(std::ostream& fp, const Service::Ptr& s fp << "\t" "}" "\n" "\n"; - - BOOST_FOREACH(const Service::Ptr& parent, service->GetParentServices()) { - Host::Ptr host = service->GetHost(); - - Host::Ptr parent_host = parent->GetHost(); - - if (!parent_host) - continue; - - fp << "define servicedependency {" "\n" - "\t" "dependent_host_name" "\t" << host->GetName() << "\n" - "\t" "dependent_service_description" "\t" << service->GetShortName() << "\n" - "\t" "host_name" "\t" << parent_host->GetName() << "\n" - "\t" "service_description" "\t" << parent->GetShortName() << "\n" - "\t" "execution_failure_criteria" "\t" "n" "\n" - "\t" "notification_failure_criteria" "\t" "w,u,c" "\n" - "\t" "}" "\n" - "\n"; - } } void StatusDataWriter::DumpCustomAttributes(std::ostream& fp, const DynamicObject::Ptr& object) @@ -673,6 +655,28 @@ void StatusDataWriter::UpdateObjectsCache(void) DumpTimePeriod(objectfp, tp); } + BOOST_FOREACH(const Dependency::Ptr& dep, DynamicType::GetObjects()) { + Service::Ptr parent_service = dep->GetParentService(); + + if (!parent_service) + continue; + + Service::Ptr child_service = dep->GetChildService(); + + if (!child_service) + continue; + + objectfp << "define servicedependency {" "\n" + "\t" "dependent_host_name" "\t" << child_service->GetHost()->GetName() << "\n" + "\t" "dependent_service_description" "\t" << child_service->GetShortName() << "\n" + "\t" "host_name" "\t" << parent_service->GetHost()->GetName() << "\n" + "\t" "service_description" "\t" << parent_service->GetShortName() << "\n" + "\t" "execution_failure_criteria" "\t" "n" "\n" + "\t" "notification_failure_criteria" "\t" "w,u,c" "\n" + "\t" "}" "\n" + "\n"; + } + objectfp.close(); #ifdef _WIN32 diff --git a/doc/4.3-object-types.md b/doc/4.3-object-types.md index 911c0490e..d9d12917c 100644 --- a/doc/4.3-object-types.md +++ b/doc/4.3-object-types.md @@ -17,12 +17,6 @@ Example: groups = [ "all-hosts" ], - host_dependencies = [ "router" ], - - service_dependencies = [ - { host = "db-server", service = "mysql" } - ], - services["ping"] = { templates = [ "ping" ] }, @@ -46,9 +40,8 @@ Attributes: display_name |**Optional.** A short description of the host. check |**Optional.** A service that is used to determine whether the host is up or down. This must be a service short name of a service that belongs to the host. groups |**Optional.** A list of host groups this host belongs to. - host_dependencies|**Optional.** A list of host names which this host depends on. These dependencies are used to determine whether the host is unreachable. - service_dependencies|**Optional.** A list of services which this host depends on. Each array element must be a dictionary containing the keys "host" and "service". These dependencies are used to determine whether the host is unreachable. services |**Optional.** Inline definition of services. Each dictionary item specifies a service.

The `templates` attribute can be used to specify an array of templates that should be inherited by the service.

The new service's name is "hostname!service" - where "service" is the dictionary key in the services dictionary.

The dictionary key is used as the service's short name. + dependencies |**Optional.** Inline definition of dependencies. Each dictionary item specifies a dependency.

The `templates` attribute can be used to specify an array of templates that should be inherited by the dependency object.

The new dependency object's name is "hostname:service:dependency" - where "dependency" is the dictionary key in the dependencies dictionary. macros |**Optional.** A dictionary containing macros that are specific to this host. ### HostGroup @@ -121,10 +114,9 @@ Attributes: event\_command |**Optional.** The name of an event command that should be executed every time the service's state changes. flapping\_threshold|**Optional.** The flapping threshold in percent when a service is considered to be flapping. volatile |**Optional.** The volatile setting enables always `HARD` state types if `NOT-OK` state changes occur. - host_dependencies|**Optional.** A list of host names which this host depends on. These dependencies are used to determine whether the host is unreachable. - service_dependencies|**Optional.** A list of services which this host depends on. Each array element must be a dictionary containing the keys "host" and "service". These dependencies are used to determine whether the host is unreachable. groups |**Optional.** The service groups this service belongs to. notifications |**Optional.** Inline definition of notifications. Each dictionary item specifies a notification.

The `templates` attribute can be used to specify an array of templates that should be inherited by the notification object.

The new notification object's name is "hostname:service:notification" - where "notification" is the dictionary key in the notifications dictionary. + dependencies |**Optional.** Inline definition of dependencies. Each dictionary item specifies a dependency.

The `templates` attribute can be used to specify an array of templates that should be inherited by the dependency object.

The new dependency object's name is "hostname:service:dependency" - where "dependency" is the dictionary key in the dependencies dictionary. authorities |**Optional.** A list of Endpoints on which this service check will be executed in a cluster scenario. ### ServiceGroup @@ -202,6 +194,50 @@ Available notification type and state filters: > > In order to notify on problem states, you will need the type filter `NotificationFilterProblem`. +### Dependency + +Dependency objects are used to specify dependencies between hosts and services. + +> **Best Practice** +> +> Rather than creating a `Dependency` object for a specific service it is usually easier +> to just create a `Dependency` template and using the `dependencies` attribute in the `Service` +> object to associate these templates with a service. + +Example: + + object Dependency "webserver-internet" { + child_host = "webserver", + child_service = "ping4", + + parent_host = "internet", + parent_service = "ping4", + + state_filter = (StateFilterOK), + + disable_checks = true + } + +Attributes: + + Name |Description + ----------------|---------------- + parent_host |**Required.** The parent host. + parent_service |**Optional.** The parent service. When not specified the host's check service is used. + child_host |**Required.** The child host. + child_service |**Optional.** The child service. When not specified the host's check service is used. + disable_checks |**Optional.** Whether to disable checks when this dependency fails. Defaults to false. + disable_notifications|**Optional.** Whether to disable notifications when this dependency fails. Defaults to true. + period |**Optional.** Time period during which this dependency is enabled. + state_filter |**Optional.** A set of type filters when this dependency should be OK. Defaults to (StateFilterOK | StateFilterWarning). + +Available state filters: + + StateFilterOK + StateFilterWarning + StateFilterCritical + StateFilterUnknown + ### User A user. diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index 95555c1f4..907791d88 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -19,6 +19,7 @@ mkclass_target(checkcommand.ti checkcommand.th) mkclass_target(checkresult.ti checkresult.th) mkclass_target(command.ti command.th) mkclass_target(comment.ti comment.th) +mkclass_target(dependency.ti dependency.th) mkclass_target(domain.ti domain.th) mkclass_target(downtime.ti downtime.th) mkclass_target(eventcommand.ti eventcommand.th) @@ -40,7 +41,7 @@ mkembedconfig_target(icinga-type.conf icinga-type.cpp) add_library(icinga SHARED api.cpp api.h checkcommand.cpp checkcommand.th checkresult.cpp checkresult.th - cib.cpp command.cpp command.th comment.cpp comment.th compatutility.cpp + cib.cpp command.cpp command.th comment.cpp comment.th compatutility.cpp dependency.cpp dependency.th domain.cpp domain.th downtime.cpp downtime.th eventcommand.cpp eventcommand.th externalcommandprocessor.cpp host.cpp host.th hostgroup.cpp hostgroup.th icingaapplication.cpp icingaapplication.th icingastatuswriter.cpp @@ -48,7 +49,7 @@ add_library(icinga SHARED macroprocessor.cpp macroresolver.cpp notificationcommand.cpp notificationcommand.th notification.cpp notification.th perfdatavalue.cpp perfdatavalue.th pluginutility.cpp scheduleddowntime.cpp scheduleddowntime.th service-check.cpp - service-comment.cpp service.cpp service-downtime.cpp service-event.cpp + service-comment.cpp service.cpp service-dependency.cpp service-downtime.cpp service-event.cpp service-flapping.cpp service.th servicegroup.cpp servicegroup.th service-notification.cpp timeperiod.cpp timeperiod.th user.cpp user.th usergroup.cpp usergroup.th icinga-type.cpp diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp new file mode 100644 index 000000000..6807f8535 --- /dev/null +++ b/lib/icinga/dependency.cpp @@ -0,0 +1,137 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-present Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "icinga/dependency.h" +#include "base/dynamictype.h" +#include "base/objectlock.h" +#include "base/logger_fwd.h" +#include + +using namespace icinga; + +REGISTER_TYPE(Dependency); + +void Dependency::OnStateLoaded(void) +{ + DynamicObject::Start(); + + ASSERT(!OwnsLock()); + + if (!GetChildService()) + Log(LogWarning, "icinga", "Dependency '" + GetName() + "' references an invalid child service and will be ignored."); + else + GetChildService()->AddDependency(GetSelf()); + + if (!GetParentService()) + Log(LogWarning, "icinga", "Dependency '" + GetName() + "' references an invalid parent service and will always fail."); + else + GetParentService()->AddReverseDependency(GetSelf()); +} + +void Dependency::Stop(void) +{ + DynamicObject::Stop(); + + if (GetChildService()) + GetChildService()->RemoveDependency(GetSelf()); + + if (GetParentService()) + GetParentService()->RemoveReverseDependency(GetSelf()); +} + +bool Dependency::IsAvailable(DependencyType dt) const +{ + Service::Ptr service = GetParentService(); + + if (!service) + return false; + + /* ignore if it's the same service */ + if (service == GetChildService()) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Parent and child service are identical."); + return true; + } + + /* ignore pending services */ + if (!service->GetLastCheckResult()) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Service hasn't been checked yet."); + return true; + } + + /* ignore soft states */ + if (service->GetStateType() == StateTypeSoft) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Service is in a soft state."); + return true; + } + + /* check state */ + if ((1 << static_cast(service->GetState())) & GetStateFilter()) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Service matches state filter."); + return true; + } + + /* ignore if not in time period */ + TimePeriod::Ptr tp = GetPeriod(); + if (tp && !tp->IsInside(Utility::GetTime())) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Outside time period."); + return true; + } + + if (dt == DependencyCheckExecution && !GetDisableChecks()) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Checks are not disabled."); + return true; + } else if (dt == DependencyNotification && !GetDisableNotifications()) { + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' passed: Notifications are not disabled"); + return true; + } + + Log(LogDebug, "icinga", "Dependency '" + GetName() + "' failed."); + return false; +} + +Service::Ptr Dependency::GetChildService(void) const +{ + Host::Ptr host = Host::GetByName(GetChildHostRaw()); + + if (!host) + return Service::Ptr(); + + if (GetChildServiceRaw().IsEmpty()) + return host->GetCheckService(); + + return host->GetServiceByShortName(GetChildServiceRaw()); +} + +Service::Ptr Dependency::GetParentService(void) const +{ + Host::Ptr host = Host::GetByName(GetParentHostRaw()); + + if (!host) + return Service::Ptr(); + + if (GetParentServiceRaw().IsEmpty()) + return host->GetCheckService(); + + return host->GetServiceByShortName(GetParentServiceRaw()); +} + +TimePeriod::Ptr Dependency::GetPeriod(void) const +{ + return TimePeriod::GetByName(GetPeriodRaw()); +} diff --git a/lib/icinga/dependency.h b/lib/icinga/dependency.h new file mode 100644 index 000000000..8920dd146 --- /dev/null +++ b/lib/icinga/dependency.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-present Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef DEPENDENCY_H +#define DEPENDENCY_H + +#include "icinga/i2-icinga.h" +#include "icinga/dependency.th" +#include "icinga/service.h" +#include "base/array.h" +#include "base/dictionary.h" + +namespace icinga +{ + +/** + * A service dependency.. + * + * @ingroup icinga + */ +class I2_ICINGA_API Dependency : public ObjectImpl +{ +public: + DECLARE_PTR_TYPEDEFS(Dependency); + DECLARE_TYPENAME(Dependency); + + Service::Ptr GetParentService(void) const; + Service::Ptr GetChildService(void) const; + + TimePeriod::Ptr GetPeriod(void) const; + + bool IsAvailable(DependencyType dt) const; + +protected: + virtual void OnStateLoaded(void); + virtual void Stop(void); +}; + +} + +#endif /* DEPENDENCY_H */ diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti new file mode 100644 index 000000000..bbd89276c --- /dev/null +++ b/lib/icinga/dependency.ti @@ -0,0 +1,25 @@ +#include "base/dynamicobject.h" +#include "icinga/service.h" + +namespace icinga +{ + +class Dependency : DynamicObject +{ + [config] String child_host (ChildHostRaw); + [config] String child_service (ChildServiceRaw); + + [config] String parent_host (ParentHostRaw); + [config] String parent_service (ParentServiceRaw); + + [config] String period (PeriodRaw); + + [config] int state_filter { + default {{{ return (1 << StateOK) | (1 << StateWarning); }}} + }; + + [config] bool disable_checks; + [config] bool disable_notifications; +}; + +} diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 25e24c682..345aa3ec4 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -84,54 +84,15 @@ void Host::Stop(void) // TODO: unregister slave services/notifications? } -bool Host::IsReachable(void) const +bool Host::IsReachable(DependencyType dt, shared_ptr *failedDependency) const { ASSERT(!OwnsLock()); - std::set parentServices = GetParentServices(); - - BOOST_FOREACH(const Service::Ptr& service, parentServices) { - ObjectLock olock(service); - - /* ignore pending services */ - if (!service->GetLastCheckResult()) - continue; - - /* ignore soft states */ - if (service->GetStateType() == StateTypeSoft) - continue; - - /* ignore services states OK and Warning */ - if (service->GetState() == StateOK || - service->GetState() == StateWarning) - continue; - - return false; - } - - std::set parentHosts = GetParentHosts(); - - BOOST_FOREACH(const Host::Ptr& host, parentHosts) { - Service::Ptr hc = host->GetCheckService(); - - /* ignore hosts that don't have a check */ - if (!hc) - continue; - - ObjectLock olock(hc); - - /* ignore soft states */ - if (hc->GetStateType() == StateTypeSoft) - continue; - - /* ignore hosts that are up */ - if (hc->GetState() == StateOK) - continue; - - return false; - } + Service::Ptr hc = GetCheckService(); + if (!hc) + return true; - return true; + return hc->IsReachable(dt, failedDependency); } void Host::UpdateSlaveServices(void) @@ -238,7 +199,14 @@ int Host::GetTotalServices(void) const Service::Ptr Host::GetServiceByShortName(const Value& name) const { - if (name.IsScalar()) { + if (name.IsEmpty()) { + Service::Ptr hc = GetCheckService(); + + if (!hc) + BOOST_THROW_EXCEPTION(std::invalid_argument("Host does not have a host check service: " + GetName())); + + return hc; + } else if (name.IsScalar()) { { boost::mutex::scoped_lock lock(m_ServicesMutex); @@ -259,73 +227,58 @@ Service::Ptr Host::GetServiceByShortName(const Value& name) const } } -std::set Host::GetParentHosts(void) const +Service::Ptr Host::GetCheckService(void) const { - std::set parents; - - Array::Ptr dependencies = GetHostDependencies(); + String host_check = GetCheck(); - if (dependencies) { - ObjectLock olock(dependencies); + if (host_check.IsEmpty()) + return Service::Ptr(); - BOOST_FOREACH(const Value& value, dependencies) { - if (value == GetName()) - continue; + return GetServiceByShortName(host_check); +} - Host::Ptr host = GetByName(value); +std::set Host::GetParentHosts(void) const +{ + std::set result; + Service::Ptr hc = GetCheckService(); - parents.insert(host); - } - } + if (hc) + result = hc->GetParentHosts(); - return parents; + return result; } std::set Host::GetChildHosts(void) const { - std::set children; - - BOOST_FOREACH(const Host::Ptr& host, DynamicType::GetObjects()) { - Array::Ptr dependencies = host->GetHostDependencies(); - - if (dependencies) { - ObjectLock olock(dependencies); + std::set result; + Service::Ptr hc = GetCheckService(); - BOOST_FOREACH(const Value& value, dependencies) { - if (value == GetName()) - children.insert(host); - } - } - } + if (hc) + result = hc->GetChildHosts(); - return children; + return result; } -Service::Ptr Host::GetCheckService(void) const +std::set Host::GetParentServices(void) const { - String host_check = GetCheck(); + std::set result; + Service::Ptr hc = GetCheckService(); - if (host_check.IsEmpty()) - return Service::Ptr(); + if (hc) + result = hc->GetParentServices(); - return GetServiceByShortName(host_check); + return result; } -std::set Host::GetParentServices(void) const +std::set Host::GetChildServices(void) const { - std::set parents; - - Array::Ptr dependencies = GetServiceDependencies(); - - if (dependencies) { - ObjectLock olock(dependencies); + std::set result; + Service::Ptr hc = GetCheckService(); - BOOST_FOREACH(const Value& value, dependencies) { - parents.insert(GetServiceByShortName(value)); - } - } + if (hc) + result = hc->GetChildServices(); - return parents; + return result; } HostState Host::CalculateState(ServiceState state, bool reachable) diff --git a/lib/icinga/host.h b/lib/icinga/host.h index 05c4e12d3..904a5edbe 100644 --- a/lib/icinga/host.h +++ b/lib/icinga/host.h @@ -31,6 +31,7 @@ namespace icinga { class Service; +class Dependency; /** * The state of a host. @@ -44,6 +45,13 @@ enum HostState HostUnreachable = 2 }; +enum DependencyType +{ + DependencyState, + DependencyCheckExecution, + DependencyNotification +}; + /** * An Icinga host. * @@ -56,11 +64,13 @@ public: DECLARE_TYPENAME(Host); shared_ptr GetCheckService(void) const; + std::set GetParentHosts(void) const; std::set GetChildHosts(void) const; std::set > GetParentServices(void) const; + std::set > GetChildServices(void) const; - bool IsReachable() const; + bool IsReachable(DependencyType dt = DependencyState, shared_ptr *failedDependency = NULL) const; shared_ptr GetServiceByShortName(const Value& name) const; diff --git a/lib/icinga/host.ti b/lib/icinga/host.ti index edb4d4526..a82b3a877 100644 --- a/lib/icinga/host.ti +++ b/lib/icinga/host.ti @@ -15,10 +15,9 @@ class Host : DynamicObject }; [config] Array::Ptr groups; [config] Dictionary::Ptr macros; - [config] Array::Ptr host_dependencies; - [config] Array::Ptr service_dependencies; [config] String check; [config, protected] Dictionary::Ptr services (ServiceDescriptions); + [config] Dictionary::Ptr dependencies (DependencyDescriptions); }; } diff --git a/lib/icinga/icinga-type.conf b/lib/icinga/icinga-type.conf index e273f79f4..e969923e3 100644 --- a/lib/icinga/icinga-type.conf +++ b/lib/icinga/icinga-type.conf @@ -23,22 +23,20 @@ type Host { %attribute array "groups" { %attribute name(HostGroup) "*" }, - %attribute array "host_dependencies" { - %attribute name(Host) "*" - }, - %attribute array "service_dependencies" { + %attribute dictionary "services" { %attribute dictionary "*" { - %require "host", - %attribute name(Host) "host", + %attribute array "templates" { + %attribute name(Service) "*" + }, - %require "service", - %attribute string "service" + %attribute any "*" } }, - %attribute dictionary "services" { + + %attribute dictionary "dependencies" { %attribute dictionary "*" { %attribute array "templates" { - %attribute name(Service) "*" + %attribute name(Dependency) "*" }, %attribute any "*" @@ -95,18 +93,16 @@ type Service { %attribute number "volatile", - %attribute array "host_dependencies" { - %attribute name(Host) "*" - }, - %attribute array "service_dependencies" { + %attribute dictionary "dependencies" { %attribute dictionary "*" { - %require "host", - %attribute name(Host) "host", + %attribute array "templates" { + %attribute name(Dependency) "*" + }, - %require "service", - %attribute string "service" + %attribute any "*" } }, + %attribute array "groups" { %attribute name(ServiceGroup) "*" }, @@ -285,3 +281,19 @@ type ScheduledDowntime { %attribute any "templates" } + +type Dependency { + %require "parent_host", + %attribute name(Host) "parent_host", + %attribute string "parent_service", + + %require "child_host", + %attribute name(Host) "child_host", + %attribute string "child_service", + + %attribute name(TimePeriod) "period", + + %attribute number "state_filter", + %attribute number "disable_checks", + %attribute number "disable_notifications" +} diff --git a/lib/icinga/service-check.cpp b/lib/icinga/service-check.cpp index 84cd41a4c..aad063b7b 100644 --- a/lib/icinga/service-check.cpp +++ b/lib/icinga/service-check.cpp @@ -240,6 +240,7 @@ void Service::ProcessCheckResult(const CheckResult::Ptr& cr, const String& autho cr->SetCheckSource(authority); bool reachable = IsReachable(); + bool notification_reachable = IsReachable(DependencyNotification); bool host_reachable = GetHost()->IsReachable(); @@ -358,7 +359,7 @@ void Service::ProcessCheckResult(const CheckResult::Ptr& cr, const String& autho Service::UpdateStatistics(cr); bool in_downtime = IsInDowntime(); - bool send_notification = hardChange && reachable && !in_downtime && !IsAcknowledged(); + bool send_notification = hardChange && notification_reachable && !in_downtime && !IsAcknowledged(); if (old_state == StateOK && old_stateType == StateTypeSoft) send_notification = false; /* Don't send notifications for SOFT-OK -> HARD-OK. */ diff --git a/lib/icinga/service-dependency.cpp b/lib/icinga/service-dependency.cpp new file mode 100644 index 000000000..9a45c83c8 --- /dev/null +++ b/lib/icinga/service-dependency.cpp @@ -0,0 +1,240 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-present Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "icinga/service.h" +#include "icinga/dependency.h" +#include "config/configitembuilder.h" +#include "base/dynamictype.h" +#include "base/objectlock.h" +#include "base/logger_fwd.h" +#include "base/timer.h" +#include "base/utility.h" +#include "base/convert.h" +#include + +using namespace icinga; + +void Service::AddDependency(const Dependency::Ptr& dep) +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + m_Dependencies.insert(dep); +} + +void Service::RemoveDependency(const Dependency::Ptr& dep) +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + m_Dependencies.erase(dep); +} + +std::set Service::GetDependencies(void) const +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + return m_Dependencies; +} + +void Service::AddReverseDependency(const Dependency::Ptr& dep) +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + m_ReverseDependencies.insert(dep); +} + +void Service::RemoveReverseDependency(const Dependency::Ptr& dep) +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + m_ReverseDependencies.erase(dep); +} + +std::set Service::GetReverseDependencies(void) const +{ + boost::mutex::scoped_lock lock(m_DependencyMutex); + return m_ReverseDependencies; +} + +bool Service::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const +{ + if (rstack > 20) { + Log(LogWarning, "icinga", "Too many nested dependencies for service '" + GetName() + "': Dependency failed."); + + return false; + } + + BOOST_FOREACH(const Service::Ptr& service, GetParentServices()) { + if (!service->IsReachable(dt, failedDependency, rstack + 1)) + return false; + } + + /* implicit dependency on host's check service */ + if (dt == DependencyState || dt == DependencyNotification) { + Service::Ptr hc = GetHost()->GetCheckService(); + + if (hc && hc->GetState() == StateCritical && hc->GetStateType() == StateTypeHard) { + if (failedDependency) + *failedDependency = Dependency::Ptr(); + + return false; + } + } + + BOOST_FOREACH(const Dependency::Ptr& dep, GetDependencies()) { + if (!dep->IsAvailable(dt)) { + if (failedDependency) + *failedDependency = dep; + + return false; + } + } + + if (failedDependency) + *failedDependency = Dependency::Ptr(); + + return true; +} + +std::set Service::GetParentHosts(void) const +{ + std::set result; + + BOOST_FOREACH(const Service::Ptr& svc, GetParentServices()) + result.insert(svc->GetHost()); + + return result; +} + +std::set Service::GetChildHosts(void) const +{ + std::set result; + + BOOST_FOREACH(const Service::Ptr& svc, GetChildServices()) + result.insert(svc->GetHost()); + + return result; +} + +std::set Service::GetParentServices(void) const +{ + std::set parents; + + BOOST_FOREACH(const Dependency::Ptr& dep, GetDependencies()) { + Service::Ptr service = dep->GetParentService(); + + if (service) + parents.insert(service); + } + + return parents; +} + +std::set Service::GetChildServices(void) const +{ + std::set parents; + + BOOST_FOREACH(const Dependency::Ptr& dep, GetReverseDependencies()) { + Service::Ptr service = dep->GetChildService(); + + if (service) + parents.insert(service); + } + + return parents; +} + +void Service::UpdateSlaveDependencies(void) +{ + /* + * pass == 0 -> steal host's dependency definitions + * pass == 1 -> service's dependencies + */ + for (int pass = 0; pass < 2; pass++) { + /* Service dependency descs */ + Dictionary::Ptr descs; + + if (pass == 0 && !IsHostCheck()) + continue; + + if (pass == 0) + descs = GetHost()->GetDependencyDescriptions(); + else + GetDependencyDescriptions(); + + if (!descs || descs->GetLength() == 0) + continue; + + ConfigItem::Ptr item; + + if (pass == 0) + item = ConfigItem::GetObject("Host", GetHost()->GetName()); + else + ConfigItem::GetObject("Service", GetName()); + + ObjectLock olock(descs); + + BOOST_FOREACH(const Dictionary::Pair& kv, descs) { + std::ostringstream namebuf; + namebuf << GetName() << "!" << kv.first; + String name = namebuf.str(); + + std::vector path; + path.push_back("dependencies"); + path.push_back(kv.first); + + ExpressionList::Ptr exprl; + + { + ObjectLock ilock(item); + + exprl = item->GetLinkedExpressionList(); + } + + DebugInfo di; + exprl->FindDebugInfoPath(path, di); + + if (di.Path.IsEmpty()) + di = item->GetDebugInfo(); + + ConfigItemBuilder::Ptr builder = make_shared(di); + builder->SetType("Dependency"); + builder->SetName(name); + builder->AddExpression("child_host", OperatorSet, GetHost()->GetName()); + builder->AddExpression("child_service", OperatorSet, GetShortName()); + + Dictionary::Ptr dependency = kv.second; + + Array::Ptr templates = dependency->Get("templates"); + + if (templates) { + ObjectLock tlock(templates); + + BOOST_FOREACH(const Value& tmpl, templates) { + builder->AddParent(tmpl); + } + } + + /* Clone attributes from the scheduled downtime expression list. */ + ExpressionList::Ptr sd_exprl = make_shared(); + exprl->ExtractPath(path, sd_exprl); + + builder->AddExpressionList(sd_exprl); + + ConfigItem::Ptr dependencyItem = builder->Compile(); + dependencyItem->Register(); + DynamicObject::Ptr dobj = dependencyItem->Commit(); + dobj->OnConfigLoaded(); + } + } +} diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 6a7f3df4d..793180be1 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -23,6 +23,7 @@ #include "icinga/icingaapplication.h" #include "icinga/macroprocessor.h" #include "icinga/pluginutility.h" +#include "icinga/dependency.h" #include "config/configitembuilder.h" #include "base/dynamictype.h" #include "base/objectlock.h" @@ -77,6 +78,7 @@ void Service::OnConfigLoaded(void) UpdateSlaveNotifications(); UpdateSlaveScheduledDowntimes(); + UpdateSlaveDependencies(); SetSchedulingOffset(Utility::Random()); } @@ -138,60 +140,6 @@ bool Service::IsHostCheck(void) const } -bool Service::IsReachable(void) const -{ - ASSERT(!OwnsLock()); - - BOOST_FOREACH(const Service::Ptr& service, GetParentServices()) { - /* ignore ourselves */ - if (service->GetName() == GetName()) - continue; - - ObjectLock olock(service); - - /* ignore pending services */ - if (!service->GetLastCheckResult()) - continue; - - /* ignore soft states */ - if (service->GetStateType() == StateTypeSoft) - continue; - - /* ignore services states OK and Warning */ - if (service->GetState() == StateOK || - service->GetState() == StateWarning) - continue; - - return false; - } - - BOOST_FOREACH(const Host::Ptr& host, GetParentHosts()) { - Service::Ptr hc = host->GetCheckService(); - - /* ignore hosts that don't have a hostcheck */ - if (!hc) - continue; - - /* ignore ourselves */ - if (hc->GetName() == GetName()) - continue; - - ObjectLock olock(hc); - - /* ignore soft states */ - if (hc->GetStateType() == StateTypeSoft) - continue; - - /* ignore hosts that are up */ - if (hc->GetState() == StateOK) - continue; - - return false; - } - - return true; -} - AcknowledgementType Service::GetAcknowledgement(void) { ASSERT(OwnsLock()); @@ -239,51 +187,6 @@ void Service::ClearAcknowledgement(const String& authority) OnAcknowledgementCleared(GetSelf(), authority); } -std::set Service::GetParentHosts(void) const -{ - std::set parents; - - Host::Ptr host = GetHost(); - - /* The service's host is implicitly a parent. */ - parents.insert(host); - - Array::Ptr dependencies = GetHostDependencies(); - - if (dependencies) { - ObjectLock olock(dependencies); - - BOOST_FOREACH(const String& dependency, dependencies) { - parents.insert(Host::GetByName(dependency)); - } - } - - return parents; -} - -std::set Service::GetParentServices(void) const -{ - std::set parents; - - Host::Ptr host = GetHost(); - Array::Ptr dependencies = GetServiceDependencies(); - - if (host && dependencies) { - ObjectLock olock(dependencies); - - BOOST_FOREACH(const Value& dependency, dependencies) { - Service::Ptr service = host->GetServiceByShortName(dependency); - - if (!service || service->GetName() == GetName()) - continue; - - parents.insert(service); - } - } - - return parents; -} - bool Service::GetEnablePerfdata(void) const { if (!GetOverrideEnablePerfdata().IsEmpty()) diff --git a/lib/icinga/service.h b/lib/icinga/service.h index e65a98e21..017d50010 100644 --- a/lib/icinga/service.h +++ b/lib/icinga/service.h @@ -96,11 +96,13 @@ public: Host::Ptr GetHost(void) const; std::set GetParentHosts(void) const; + std::set GetChildHosts(void) const; std::set GetParentServices(void) const; + std::set GetChildServices(void) const; bool IsHostCheck(void) const; - bool IsReachable(void) const; + bool IsReachable(DependencyType dt = DependencyState, shared_ptr *failedDependency = NULL, int rstack = 0) const; AcknowledgementType GetAcknowledgement(void); @@ -273,6 +275,17 @@ public: bool GetEnablePerfdata(void) const; void SetEnablePerfdata(bool enabled, const String& authority = String()); + /* Dependencies */ + void AddDependency(const shared_ptr& dep); + void RemoveDependency(const shared_ptr& dep); + std::set > GetDependencies(void) const; + + void AddReverseDependency(const shared_ptr& dep); + void RemoveReverseDependency(const shared_ptr& dep); + std::set > GetReverseDependencies(void) const; + + void UpdateSlaveDependencies(void); + protected: virtual void Start(void); @@ -297,6 +310,11 @@ private: /* Notifications */ std::set m_Notifications; + + /* Dependencies */ + mutable boost::mutex m_DependencyMutex; + std::set > m_Dependencies; + std::set > m_ReverseDependencies; }; } diff --git a/lib/icinga/service.ti b/lib/icinga/service.ti index 0f91b1307..c47a64163 100644 --- a/lib/icinga/service.ti +++ b/lib/icinga/service.ti @@ -30,8 +30,6 @@ class Service : DynamicObject }}} }; [config] Dictionary::Ptr macros; - [config] Array::Ptr host_dependencies; - [config] Array::Ptr service_dependencies; [config] Array::Ptr groups; [config, protected] String check_command (CheckCommandRaw); [config] int max_check_attempts (MaxCheckAttemptsRaw) { @@ -60,6 +58,7 @@ class Service : DynamicObject }; [config] Dictionary::Ptr notifications (NotificationDescriptions); [config] Dictionary::Ptr scheduled_downtimes (ScheduledDowntimeDescriptions); + [config] Dictionary::Ptr dependencies (DependencyDescriptions); [config] bool enable_active_checks (EnableActiveChecksRaw) { default {{{ return true; }}} }; -- 2.40.0