/****************************************************************************** * Icinga 2 * * Copyright (C) 2012 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 "i2-icinga.h" using namespace icinga; static AttributeDescription serviceAttributes[] = { { "scheduling_offset", Attribute_Transient }, { "first_check", Attribute_Transient }, { "next_check", Attribute_Replicated }, { "checker", Attribute_Replicated }, { "check_attempt", Attribute_Replicated }, { "state", Attribute_Replicated }, { "state_type", Attribute_Replicated }, { "last_result", Attribute_Replicated }, { "last_state_change", Attribute_Replicated }, { "last_hard_state_change", Attribute_Replicated }, { "enable_active_checks", Attribute_Replicated }, { "enable_passive_checks", Attribute_Replicated }, { "force_next_check", Attribute_Replicated }, { "acknowledgement", Attribute_Replicated }, { "acknowledgement_expiry", Attribute_Replicated }, { "downtimes", Attribute_Replicated }, { "comments", Attribute_Replicated } }; REGISTER_TYPE(Service, serviceAttributes); const int Service::DefaultMaxCheckAttempts = 3; const int Service::DefaultCheckInterval = 5 * 60; const int Service::CheckIntervalDivisor = 5; boost::signal Service::OnCheckResultReceived; boost::signal Service::OnCheckerChanged; boost::signal Service::OnNextCheckChanged; Service::Service(const Dictionary::Ptr& serializedObject) : DynamicObject(serializedObject) { ServiceGroup::InvalidateMembersCache(); Host::InvalidateServicesCache(); Service::InvalidateDowntimeCache(); Service::InvalidateCommentCache(); } Service::~Service(void) { ServiceGroup::InvalidateMembersCache(); Host::InvalidateServicesCache(); Service::InvalidateDowntimeCache(); Service::InvalidateCommentCache(); } String Service::GetAlias(void) const { String value = Get("alias"); if (!value.IsEmpty()) return value; return GetName(); } bool Service::Exists(const String& name) { return (DynamicObject::GetObject("Service", name)); } Service::Ptr Service::GetByName(const String& name) { DynamicObject::Ptr configObject = DynamicObject::GetObject("Service", name); if (!configObject) BOOST_THROW_EXCEPTION(invalid_argument("Service '" + name + "' does not exist.")); return dynamic_pointer_cast(configObject); } Service::Ptr Service::GetByNamePair(const String& hostName, const String& serviceName) { if (!hostName.IsEmpty()) { Host::Ptr host = Host::GetByName(hostName); return host->GetServiceByShortName(serviceName); } else { return Service::GetByName(serviceName); } } Host::Ptr Service::GetHost(void) const { String hostname = Get("host_name"); if (hostname.IsEmpty()) BOOST_THROW_EXCEPTION(runtime_error("Service object is missing the 'host_name' property.")); return Host::GetByName(hostname); } Dictionary::Ptr Service::GetMacros(void) const { return Get("macros"); } Dictionary::Ptr Service::GetDowntimes(void) const { Service::ValidateDowntimeCache(); return Get("downtimes"); } Dictionary::Ptr Service::GetComments(void) const { Service::ValidateCommentCache(); return Get("comments"); } String Service::GetCheckCommand(void) const { return Get("check_command"); } long Service::GetMaxCheckAttempts(void) const { Value value = Get("max_check_attempts"); if (value.IsEmpty()) return DefaultMaxCheckAttempts; return value; } double Service::GetCheckInterval(void) const { Value value = Get("check_interval"); if (value.IsEmpty()) return DefaultCheckInterval; return value; } double Service::GetRetryInterval(void) const { Value value = Get("retry_interval"); if (value.IsEmpty()) return GetCheckInterval() / CheckIntervalDivisor; return value; } Dictionary::Ptr Service::GetHostDependencies(void) const { return Get("hostdependencies"); } Dictionary::Ptr Service::GetServiceDependencies(void) const { return Get("servicedependencies"); } Dictionary::Ptr Service::GetGroups(void) const { return Get("servicegroups"); } Dictionary::Ptr Service::GetCheckers(void) const { return Get("checkers"); } String Service::GetShortName(void) const { Value value = Get("short_name"); if (value.IsEmpty()) return GetName(); return value; } bool Service::IsReachable(void) const { BOOST_FOREACH(const Service::Ptr& service, GetParentServices()) { /* 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()) { /* ignore hosts that are up */ if (host->IsUp()) continue; return false; } return true; } bool Service::IsInDowntime(void) const { Dictionary::Ptr downtimes = GetDowntimes(); if (!downtimes) return false; Dictionary::Ptr downtime; BOOST_FOREACH(tie(tuples::ignore, downtime), downtimes) { if (Service::IsDowntimeActive(downtime)) return true; } return false; } void Service::SetSchedulingOffset(long offset) { Set("scheduling_offset", offset); } long Service::GetSchedulingOffset(void) { Value value = Get("scheduling_offset"); if (value.IsEmpty()) { value = rand(); SetSchedulingOffset(value); } return value; } void Service::SetFirstCheck(bool first) { Set("first_check", first ? 1 : 0); } bool Service::GetFirstCheck(void) const { Value value = Get("first_check"); if (value.IsEmpty()) return true; return static_cast(value); } void Service::SetNextCheck(double nextCheck) { Set("next_check", nextCheck); } double Service::GetNextCheck(void) { Value value = Get("next_check"); if (value.IsEmpty()) { UpdateNextCheck(); value = Get("next_check"); if (value.IsEmpty()) BOOST_THROW_EXCEPTION(runtime_error("Failed to schedule next check.")); } return value; } void Service::UpdateNextCheck(void) { double interval; if (GetStateType() == StateTypeSoft) interval = GetRetryInterval(); else interval = GetCheckInterval(); double now = Utility::GetTime(); double adj = 0; if (interval > 1) adj = fmod(now + GetSchedulingOffset(), interval); SetNextCheck(now - adj + interval); } void Service::SetChecker(const String& checker) { Set("checker", checker); } String Service::GetChecker(void) const { return Get("checker"); } void Service::SetCurrentCheckAttempt(long attempt) { Set("check_attempt", attempt); } long Service::GetCurrentCheckAttempt(void) const { Value value = Get("check_attempt"); if (value.IsEmpty()) return 1; return value; } void Service::SetState(ServiceState state) { Set("state", static_cast(state)); } ServiceState Service::GetState(void) const { Value value = Get("state"); if (value.IsEmpty()) return StateUnknown; int ivalue = static_cast(value); return static_cast(ivalue); } void Service::SetStateType(ServiceStateType type) { Set("state_type", static_cast(type)); } ServiceStateType Service::GetStateType(void) const { Value value = Get("state_type"); if (value.IsEmpty()) return StateTypeSoft; int ivalue = static_cast(value); return static_cast(ivalue); } void Service::SetLastCheckResult(const Dictionary::Ptr& result) { Set("last_result", result); } Dictionary::Ptr Service::GetLastCheckResult(void) const { return Get("last_result"); } void Service::SetLastStateChange(double ts) { Set("last_state_change", static_cast(ts)); } double Service::GetLastStateChange(void) const { Value value = Get("last_state_change"); if (value.IsEmpty()) return IcingaApplication::GetInstance()->GetStartTime(); return value; } void Service::SetLastHardStateChange(double ts) { Set("last_hard_state_change", ts); } double Service::GetLastHardStateChange(void) const { Value value = Get("last_hard_state_change"); if (value.IsEmpty()) value = IcingaApplication::GetInstance()->GetStartTime(); return value; } bool Service::GetEnableActiveChecks(void) const { Value value = Get("enable_active_checks"); if (value.IsEmpty()) return true; return static_cast(value); } void Service::SetEnablePassiveChecks(bool enabled) { Set("enable_passive_checks", enabled ? 1 : 0); } bool Service::GetEnablePassiveChecks(void) const { Value value = Get("enable_passive_checks"); if (value.IsEmpty()) return true; return static_cast(value); } void Service::SetEnableActiveChecks(bool enabled) { Set("enable_active_checks", enabled ? 1 : 0); } bool Service::GetForceNextCheck(void) const { Value value = Get("force_next_check"); if (value.IsEmpty()) return false; return static_cast(value); } void Service::SetForceNextCheck(bool forced) { Set("force_next_check", forced ? 1 : 0); } AcknowledgementType Service::GetAcknowledgement(void) { Value value = Get("acknowledgement"); if (value.IsEmpty()) return AcknowledgementNone; int ivalue = static_cast(value); AcknowledgementType avalue = static_cast(ivalue); if (avalue != AcknowledgementNone) { double expiry = GetAcknowledgementExpiry(); if (expiry != 0 && expiry < Utility::GetTime()) { avalue = AcknowledgementNone; SetAcknowledgement(avalue); SetAcknowledgementExpiry(0); } } return avalue; } void Service::SetAcknowledgement(AcknowledgementType acknowledgement) { Set("acknowledgement", static_cast(acknowledgement)); } double Service::GetAcknowledgementExpiry(void) const { Value value = Get("acknowledgement_expiry"); if (value.IsEmpty()) return 0; return static_cast(value); } void Service::SetAcknowledgementExpiry(double timestamp) { Set("acknowledgement_expiry", timestamp); } void Service::ApplyCheckResult(const Dictionary::Ptr& cr) { ServiceState old_state = GetState(); ServiceStateType old_stateType = GetStateType(); long attempt = GetCurrentCheckAttempt(); if (cr->Get("state") == StateOK) { if (GetState() == StateOK) SetStateType(StateTypeHard); attempt = 1; } else { if (attempt >= GetMaxCheckAttempts()) { SetStateType(StateTypeHard); attempt = 1; } else if (GetStateType() == StateTypeSoft || GetState() == StateOK) { SetStateType(StateTypeSoft); attempt++; } } SetCurrentCheckAttempt(attempt); int state = cr->Get("state"); SetState(static_cast(state)); SetLastCheckResult(cr); if (old_state != GetState()) { double now = Utility::GetTime(); SetLastStateChange(now); if (old_stateType != GetStateType()) SetLastHardStateChange(now); /* remove acknowledgements */ if (GetAcknowledgement() == AcknowledgementNormal || (GetAcknowledgement() == AcknowledgementSticky && GetStateType() == StateTypeHard && GetState() == StateOK)) { SetAcknowledgement(AcknowledgementNone); SetAcknowledgementExpiry(0); } /* reschedule service dependencies */ BOOST_FOREACH(const Service::Ptr& parent, GetParentServices()) { parent->SetNextCheck(Utility::GetTime()); } /* reschedule host dependencies */ BOOST_FOREACH(const Host::Ptr& parent, GetParentHosts()) { Service::Ptr service = parent->GetHostCheckService(); if (service) service->SetNextCheck(Utility::GetTime()); } // TODO: notify our child services/hosts that our state has changed } if (GetState() != StateOK) TriggerDowntimes(); } ServiceState Service::StateFromString(const String& state) { if (state == "ok") return StateOK; else if (state == "warning") return StateWarning; else if (state == "critical") return StateCritical; else if (state == "uncheckable") return StateUncheckable; else return StateUnknown; } String Service::StateToString(ServiceState state) { switch (state) { case StateOK: return "ok"; case StateWarning: return "warning"; case StateCritical: return "critical"; case StateUncheckable: return "uncheckable"; case StateUnknown: default: return "unknown"; } } ServiceStateType Service::StateTypeFromString(const String& type) { if (type == "soft") return StateTypeSoft; else return StateTypeHard; } String Service::StateTypeToString(ServiceStateType type) { if (type == StateTypeSoft) return "soft"; else return "hard"; } bool Service::IsAllowedChecker(const String& checker) const { Dictionary::Ptr checkers = GetCheckers(); if (!checkers) return true; Value pattern; BOOST_FOREACH(tie(tuples::ignore, pattern), checkers) { if (Utility::Match(pattern, checker)) return true; } return false; } void Service::OnAttributeChanged(const String& name, const Value& oldValue) { if (name == "checker") OnCheckerChanged(GetSelf(), oldValue); else if (name == "next_check") OnNextCheckChanged(GetSelf(), oldValue); else if (name == "servicegroups") ServiceGroup::InvalidateMembersCache(); else if (name == "host_name" || name == "short_name") Host::InvalidateServicesCache(); else if (name == "downtimes") Service::InvalidateDowntimeCache(); else if (name == "comments") Service::InvalidateCommentCache(); } void Service::BeginExecuteCheck(const function& callback) { /* don't run another check if there is one pending */ if (!Get("current_task").IsEmpty()) { /* we need to call the callback anyway */ callback(); return; } /* keep track of scheduling info in case the check type doesn't provide its own information */ Dictionary::Ptr scheduleInfo = boost::make_shared(); scheduleInfo->Set("schedule_start", GetNextCheck()); scheduleInfo->Set("execution_start", Utility::GetTime()); try { vector arguments; arguments.push_back(static_cast(GetSelf())); ScriptTask::Ptr task; task = InvokeMethod("check", arguments, boost::bind(&Service::CheckCompletedHandler, this, scheduleInfo, _1, callback)); if (!task->IsFinished()) Set("current_task", task); } catch (...) { /* something went wrong while setting up the method call - * reschedule the service and call the callback anyway. */ UpdateNextCheck(); callback(); throw; } } void Service::CheckCompletedHandler(const Dictionary::Ptr& scheduleInfo, const ScriptTask::Ptr& task, const function& callback) { Set("current_task", Empty); scheduleInfo->Set("execution_end", Utility::GetTime()); scheduleInfo->Set("schedule_end", Utility::GetTime()); Dictionary::Ptr result; try { Value vresult = task->GetResult(); if (vresult.IsObjectType()) result = vresult; } catch (const exception& ex) { stringstream msgbuf; msgbuf << "Exception occured during check for service '" << GetName() << "': " << diagnostic_information(ex); String message = msgbuf.str(); Logger::Write(LogWarning, "checker", message); result = boost::make_shared(); result->Set("state", StateUnknown); result->Set("output", message); } if (result) { if (!result->Contains("schedule_start")) result->Set("schedule_start", scheduleInfo->Get("schedule_start")); if (!result->Contains("schedule_end")) result->Set("schedule_end", scheduleInfo->Get("schedule_end")); if (!result->Contains("execution_start")) result->Set("execution_start", scheduleInfo->Get("execution_start")); if (!result->Contains("execution_end")) result->Set("execution_end", scheduleInfo->Get("execution_end")); if (!result->Contains("active")) result->Set("active", 1); ProcessCheckResult(result); } /* figure out when the next check is for this service; the call to * ApplyCheckResult() should've already done this but lets do it again * just in case there was no check result. */ UpdateNextCheck(); callback(); } void Service::ProcessCheckResult(const Dictionary::Ptr& cr) { ApplyCheckResult(cr); /* flush the current transaction so other instances see the service's * new state when they receive the CheckResult message */ DynamicObject::FlushTx(); RequestMessage rm; rm.SetMethod("checker::CheckResult"); /* TODO: add _old_ state to message */ CheckResultMessage params; params.SetService(GetName()); params.SetCheckResult(cr); rm.SetParams(params); EndpointManager::GetInstance()->SendMulticastMessage(rm); } set Service::GetParentHosts(void) const { set parents; /* The service's host is implicitly a parent. */ parents.insert(GetHost()); Dictionary::Ptr dependencies = GetHostDependencies(); if (dependencies) { String key; BOOST_FOREACH(tie(key, tuples::ignore), dependencies) { parents.insert(Host::GetByName(key)); } } return parents; } set Service::GetParentServices(void) const { set parents; Dictionary::Ptr dependencies = GetServiceDependencies(); if (dependencies) { String key; Value value; BOOST_FOREACH(tie(key, value), dependencies) { Service::Ptr service = GetHost()->GetServiceByShortName(value); if (service->GetName() == GetName()) continue; parents.insert(service); } } return parents; }