*/
void NotificationComponent::Start(void)
{
+ m_Endpoint = Endpoint::MakeEndpoint("notification", false);
+ m_Endpoint->RegisterTopicHandler("icinga::SendNotifications",
+ boost::bind(&NotificationComponent::SendNotificationsRequestHandler, this, _2,
+ _3));
+
m_NotificationTimer = boost::make_shared<Timer>();
m_NotificationTimer->SetInterval(5);
m_NotificationTimer->OnTimerExpired.connect(boost::bind(&NotificationComponent::NotificationTimerHandler, this));
{
// TODO: implement
}
+
+
+/**
+ * Processes icinga::SendNotifications messages.
+ */
+void NotificationComponent::SendNotificationsRequestHandler(const Endpoint::Ptr& sender,
+ const RequestMessage& request)
+{
+ MessagePart params;
+ if (!request.GetParams(¶ms))
+ return;
+
+ String svc;
+ if (!params.Get("service", &svc))
+ return;
+
+ int type;
+ if (!params.Get("type", &type))
+ return;
+
+ Service::Ptr service = Service::GetByName(svc);
+ service->SendNotifications(static_cast<NotificationType>(type));
+}
private:
Timer::Ptr m_NotificationTimer;
+ Endpoint::Ptr m_Endpoint;
void NotificationTimerHandler(void);
+ void SendNotificationsRequestHandler(const Endpoint::Ptr& sender, const RequestMessage& request);
};
}
}
},
+ %attribute dictionary "notifications" {
+ %attribute string "*",
+ %attribute dictionary "*" {
+ %require "notification",
+ %attribute string "notification",
+
+ %attribute dictionary "macros" {
+ %attribute string "*"
+ }
+ }
+ },
+
/* service attributes */
%attribute number "max_check_attempts",
%attribute number "check_interval",
%attribute dictionary "methods" {
%require "check",
%attribute string "check"
+ },
+
+ %attribute dictionary "notifications" {
+ %attribute string "*",
+ %attribute dictionary "*" {
+ %require "notification",
+ %attribute string "notification",
+
+ %attribute dictionary "macros" {
+ %attribute string "*"
+ }
+ }
}
}
BOOST_THROW_EXCEPTION(invalid_argument(msgbuf.str()));
}
+ BOOST_FOREACH(const String& parent, m_Parents) {
+ ConfigItem::Ptr item = ConfigItem::GetObject(m_Type, parent);
+
+ if (!item) {
+ stringstream msgbuf;
+ msgbuf << "The parent config item '" + parent + "' does not exist: " << m_DebugInfo;
+ BOOST_THROW_EXCEPTION(invalid_argument(msgbuf.str()));
+ }
+ }
+
ExpressionList::Ptr exprl = boost::make_shared<ExpressionList>();
Expression execExpr("", OperatorExecute, m_ExpressionList, m_DebugInfo);
macroprocessor.h \
notification.cpp \
notification.h \
+ notificationrequestmessage.cpp \
+ notificationrequestmessage.h \
nullchecktask.cpp \
nullchecktask.h \
pluginchecktask.cpp \
RegisterCommand("DEL_SVC_COMMENT", &ExternalCommandProcessor::DelSvcComment);
RegisterCommand("DEL_ALL_HOST_COMMENTS", &ExternalCommandProcessor::DelAllHostComments);
RegisterCommand("DEL_ALL_SVC_COMMENTS", &ExternalCommandProcessor::DelAllSvcComments);
+ RegisterCommand("SEND_CUSTOM_HOST_NOTIFICATION", &ExternalCommandProcessor::SendCustomHostNotification);
+ RegisterCommand("SEND_CUSTOM_SVC_NOTIFICATION", &ExternalCommandProcessor::SendCustomSvcNotification);
m_Initialized = true;
}
Logger::Write(LogInformation, "icinga", "Removing all comments for service " + service->GetName());
service->RemoveAllComments();
}
+
+void ExternalCommandProcessor::SendCustomHostNotification(double time, const vector<String>& arguments)
+{
+ if (arguments.size() < 4)
+ BOOST_THROW_EXCEPTION(invalid_argument("Expected 4 arguments."));
+
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ Logger::Write(LogInformation, "icinga", "Sending custom notification for host " + host->GetName());
+ Service::Ptr service = host->GetHostCheckService();
+ if (service) {
+ service->RequestNotifications(NotificationCustom);
+ }
+}
+
+void ExternalCommandProcessor::SendCustomSvcNotification(double time, const vector<String>& arguments)
+{
+ if (arguments.size() < 5)
+ BOOST_THROW_EXCEPTION(invalid_argument("Expected 5 arguments."));
+
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ Logger::Write(LogInformation, "icinga", "Sending custom notification for service " + service->GetName());
+ service->RequestNotifications(NotificationCustom);
+}
namespace icinga
{
-
+
class I2_ICINGA_API ExternalCommandProcessor {
public:
static void Execute(const String& line);
static void DelSvcComment(double time, const vector<String>& arguments);
static void DelAllHostComments(double time, const vector<String>& arguments);
static void DelAllSvcComments(double time, const vector<String>& arguments);
+ static void SendCustomHostNotification(double time, const vector<String>& arguments);
+ static void SendCustomSvcNotification(double time, const vector<String>& arguments);
private:
typedef function<void (double time, const vector<String>& arguments)> Callback;
static void CopyServiceAttributes(TDict serviceDesc, const ConfigItemBuilder::Ptr& builder)
{
/* TODO: we only need to copy macros if this is an inline definition,
- * i.e. host->GetProperties() != service, however for now we just
+ * i.e. "typeid(serviceDesc)" != Service, however for now we just
* copy them anyway. */
Value macros = serviceDesc->Get("macros");
if (!macros.IsEmpty())
if (!item || IsAbstract())
return;
- Dictionary::Ptr oldServices = Get("convenience_services");
+ Dictionary::Ptr oldServices = Get("slave_services");
Dictionary::Ptr newServices;
newServices = boost::make_shared<Dictionary>();
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(item->GetDebugInfo());
builder->SetType("Service");
builder->SetName(name);
- builder->AddExpression("host_name", OperatorSet, item->GetName());
+ builder->AddExpression("host_name", OperatorSet, GetName());
builder->AddExpression("alias", OperatorSet, svcname);
builder->AddExpression("short_name", OperatorSet, svcname);
}
}
- Set("convenience_services", newServices);
+ Set("slave_services", newServices);
}
void Host::OnAttributeChanged(const String& name, const Value&)
HostGroup::InvalidateMembersCache();
else if (name == "services")
UpdateSlaveServices();
+ else if (name == "notifications") {
+ BOOST_FOREACH(const Service::Ptr& service, GetServices()) {
+ service->UpdateSlaveNotifications();
+ }
+ }
}
set<Service::Ptr> Host::GetServices(void) const
#include "icingaapplication.h"
#include "notification.h"
+#include "notificationrequestmessage.h"
#include "host.h"
#include "hostgroup.h"
<ClCompile Include="icingaapplication.cpp" />
<ClCompile Include="macroprocessor.cpp" />
<ClCompile Include="notification.cpp" />
+ <ClCompile Include="notificationrequestmessage.cpp" />
<ClCompile Include="pluginchecktask.cpp" />
<ClCompile Include="nullchecktask.cpp" />
<ClCompile Include="pluginnotificationtask.cpp" />
- <ClCompile Include="service-notifications.cpp" />
+ <ClCompile Include="service-notification.cpp" />
<ClCompile Include="service.cpp" />
<ClCompile Include="service-comment.cpp" />
<ClCompile Include="service-downtime.cpp" />
<ClInclude Include="icingaapplication.h" />
<ClInclude Include="macroprocessor.h" />
<ClInclude Include="notification.h" />
+ <ClInclude Include="notificationrequestmessage.h" />
<ClInclude Include="pluginchecktask.h" />
<ClInclude Include="nullchecktask.h" />
<ClInclude Include="pluginnotificationtask.h" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
Notification::Notification(const Dictionary::Ptr& properties)
: DynamicObject(properties)
-{ }
+{
+ Service::InvalidateNotificationsCache();
+}
Notification::~Notification(void)
-{ }
+{
+ Service::InvalidateNotificationsCache();
+}
bool Notification::Exists(const String& name)
{
return Get("macros");
}
-void Notification::SendNotification(void)
+void Notification::SendNotification(NotificationType type)
{
vector<Value> arguments;
arguments.push_back(static_cast<Notification::Ptr>(GetSelf()));
+ arguments.push_back(type);
ScriptTask::Ptr task;
task = InvokeMethod("notify", arguments, boost::bind(&Notification::NotificationCompletedHandler, this, _1));
+ if (!task) {
+ Logger::Write(LogWarning, "icinga", "Notification object '" + GetName() + "' doesn't have a 'notify' method.");
+
+ return;
+ }
+
if (!task->IsFinished()) {
/* We need to keep the task object alive until the completion handler is called. */
*/
enum NotificationType
{
- NotificationHost,
- NotificationService
+ NotificationDowntimeStart,
+ NotificationDowntimeEnd,
+ NotificationDowntimeRemoved,
+ NotificationCustom,
+ NotificationStateChange
};
class Service;
String GetNotificationCommand(void) const;
Dictionary::Ptr GetMacros(void) const;
- void SendNotification(void);
+ void SendNotification(NotificationType type);
private:
set<ScriptTask::Ptr> m_Tasks;
using namespace icinga;
-map<String, set<Notification::WeakPtr> > Service::m_NotificationsCache;
-bool Service::m_NotificationsCacheValid;
-
-void Service::SendNotifications(void) const
+String NotificationRequestMessage::GetService(void) const
{
- BOOST_FOREACH(const Notification::Ptr& notification, GetNotifications()) {
- notification->SendNotification();
- }
+ String service;
+ Get("service", &service);
+ return service;
}
-void Service::InvalidateNotificationsCache(void)
+void NotificationRequestMessage::SetService(const String& service)
{
- m_NotificationsCacheValid = false;
- m_NotificationsCache.clear();
+ Set("service", service);
}
-void Service::ValidateNotificationsCache(void)
+NotificationType NotificationRequestMessage::GetType(void) const
{
- if (m_NotificationsCacheValid)
- return;
-
- m_NotificationsCache.clear();
-
- DynamicObject::Ptr object;
- BOOST_FOREACH(tie(tuples::ignore, object), DynamicType::GetByName("Notification")->GetObjects()) {
- const Notification::Ptr& notification = static_pointer_cast<Notification>(object);
-
- m_NotificationsCache[notification->GetService()->GetName()].insert(notification);
- }
-
- m_NotificationsCacheValid = true;
+ int type;
+ Get("type", &type);
+ return static_cast<NotificationType>(type);
}
-set<Notification::Ptr> Service::GetNotifications(void) const
+void NotificationRequestMessage::SetType(NotificationType type)
{
- set<Notification::Ptr> notifications;
-
- ValidateNotificationsCache();
-
- BOOST_FOREACH(const Notification::WeakPtr& wservice, m_NotificationsCache[GetName()]) {
- Notification::Ptr notification = wservice.lock();
-
- if (!notification)
- continue;
-
- notifications.insert(notification);
- }
-
- return notifications;
+ Set("type", type);
}
--- /dev/null
+/******************************************************************************
+ * 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. *
+ ******************************************************************************/
+
+#ifndef NOTIFICATIONREQUESTMESSAGE_H
+#define NOTIFICATIONREQUESTMESSAGE_H
+
+namespace icinga
+{
+
+/**
+ * An API request for sending notifications.
+ *
+ * @ingroup icinga
+ */
+class I2_ICINGA_API NotificationRequestMessage : public MessagePart
+{
+public:
+ NotificationRequestMessage(void) : MessagePart() { }
+ NotificationRequestMessage(const MessagePart& message) : MessagePart(message) { }
+
+ String GetService(void) const;
+ void SetService(const String& service);
+
+ NotificationType GetType(void) const;
+ void SetType(NotificationType type);
+};
+
+}
+
+#endif /* NOTIFICATIONREQUESTMESSAGE_H */
REGISTER_SCRIPTFUNCTION("native::PluginNotification", &PluginNotificationTask::ScriptFunc);
-PluginNotificationTask::PluginNotificationTask(const ScriptTask::Ptr& task, const Process::Ptr& process)
- : m_Task(task), m_Process(process)
+PluginNotificationTask::PluginNotificationTask(const ScriptTask::Ptr& task, const Process::Ptr& process,
+ const String& service, const String& command)
+ : m_Task(task), m_Process(process), m_ServiceName(service), m_Command(command)
{ }
void PluginNotificationTask::ScriptFunc(const ScriptTask::Ptr& task, const vector<Value>& arguments)
if (arguments.size() < 1)
BOOST_THROW_EXCEPTION(invalid_argument("Missing argument: Notification target must be specified."));
+ if (arguments.size() < 2)
+ BOOST_THROW_EXCEPTION(invalid_argument("Missing argument: Notification type must be specified."));
+
if (!arguments[0].IsObjectType<Notification>())
BOOST_THROW_EXCEPTION(invalid_argument("Argument must be a service."));
Notification::Ptr notification = arguments[0];
+ NotificationType type = static_cast<NotificationType>(static_cast<int>(arguments[1]));
String notificationCommand = notification->GetNotificationCommand();
Process::Ptr process = boost::make_shared<Process>(command);
- PluginNotificationTask ct(task, process);
+ PluginNotificationTask ct(task, process, notification->GetService()->GetName(), command);
process->Start(boost::bind(&PluginNotificationTask::ProcessFinishedHandler, ct));
}
try {
pr = ct.m_Process->GetResult();
- if (pr.ExitStatus != 0)
- Logger::Write(LogWarning, "icinga", "Notification command failed; output: " + pr.Output);
+ if (pr.ExitStatus != 0) {
+ stringstream msgbuf;
+ msgbuf << "Notification command '" << ct.m_Command << "' for service '"
+ << ct.m_ServiceName << "' failed; exit status: "
+ << pr.ExitStatus << ", output: " << pr.Output;
+ Logger::Write(LogWarning, "icinga", msgbuf.str());
+ }
ct.m_Task->FinishResult(Empty);
} catch (...) {
static void ProcessFinishedHandler(PluginNotificationTask ct);
- PluginNotificationTask(const ScriptTask::Ptr& task, const Process::Ptr& process);
+ PluginNotificationTask(const ScriptTask::Ptr& task, const Process::Ptr& process,
+ const String& service, const String& command);
ScriptTask::Ptr m_Task;
Process::Ptr m_Process;
+
+ String m_ServiceName;
+ String m_Command;
};
}
--- /dev/null
+/******************************************************************************
+ * 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;
+
+map<String, set<Notification::WeakPtr> > Service::m_NotificationsCache;
+bool Service::m_NotificationsCacheValid;
+
+void Service::RequestNotifications(NotificationType type) const
+{
+ RequestMessage msg;
+ msg.SetMethod("icinga::SendNotifications");
+
+ NotificationRequestMessage params;
+ msg.SetParams(params);
+
+ params.SetService(GetName());
+ params.SetType(type);
+
+ Logger::Write(LogInformation, "icinga", "Sending notification anycast request for service '" + GetName() + "'");
+ EndpointManager::GetInstance()->SendAnycastMessage(Endpoint::Ptr(), msg);
+}
+
+void Service::SendNotifications(NotificationType type) const
+{
+ Logger::Write(LogInformation, "icinga", "Sending notifications for service '" + GetName() + "'");
+
+ set<Notification::Ptr> notifications = GetNotifications();
+
+ if (notifications.size() == 0)
+ Logger::Write(LogInformation, "icinga", "Service '" + GetName() + "' does not have any notifications.");
+
+ BOOST_FOREACH(const Notification::Ptr& notification, notifications) {
+ notification->SendNotification(type);
+ }
+}
+
+void Service::InvalidateNotificationsCache(void)
+{
+ m_NotificationsCacheValid = false;
+ m_NotificationsCache.clear();
+}
+
+void Service::ValidateNotificationsCache(void)
+{
+ if (m_NotificationsCacheValid)
+ return;
+
+ m_NotificationsCache.clear();
+
+ DynamicObject::Ptr object;
+ BOOST_FOREACH(tie(tuples::ignore, object), DynamicType::GetByName("Notification")->GetObjects()) {
+ const Notification::Ptr& notification = static_pointer_cast<Notification>(object);
+
+ m_NotificationsCache[notification->GetService()->GetName()].insert(notification);
+ }
+
+ m_NotificationsCacheValid = true;
+}
+
+set<Notification::Ptr> Service::GetNotifications(void) const
+{
+ set<Notification::Ptr> notifications;
+
+ ValidateNotificationsCache();
+
+ BOOST_FOREACH(const Notification::WeakPtr& wservice, m_NotificationsCache[GetName()]) {
+ Notification::Ptr notification = wservice.lock();
+
+ if (!notification)
+ continue;
+
+ notifications.insert(notification);
+ }
+
+ return notifications;
+}
+
+template<typename TDict>
+static void CopyNotificationAttributes(TDict notificationDesc, const ConfigItemBuilder::Ptr& builder)
+{
+ /* TODO: we only need to copy macros if this is an inline definition,
+ * i.e. "typeid(notificationDesc)" != Notification, however for now we just
+ * copy them anyway. */
+ Value macros = notificationDesc->Get("macros");
+ if (!macros.IsEmpty())
+ builder->AddExpression("macros", OperatorPlus, macros);
+
+ /*Value notificationInterval = notificationDesc->Get("notification_interval");
+ if (!notificationInterval.IsEmpty())
+ builder->AddExpression("notification_interval", OperatorSet, notificationInterval);*/
+}
+
+void Service::UpdateSlaveNotifications(void)
+{
+ ConfigItem::Ptr item = ConfigItem::GetObject("Service", GetName());
+
+ /* Don't create slave notifications unless we own this object
+ * and it's not a template. */
+ if (!item || IsAbstract())
+ return;
+
+ Dictionary::Ptr oldNotifications = Get("slave_notifications");
+
+ Dictionary::Ptr newNotifications;
+ newNotifications = boost::make_shared<Dictionary>();
+
+ vector<Dictionary::Ptr> notificationDescsList;
+ notificationDescsList.push_back(GetHost()->Get("notifications"));
+ notificationDescsList.push_back(Get("notifications"));
+
+ BOOST_FOREACH(const Dictionary::Ptr& notificationDescs, notificationDescsList) {
+ if (!notificationDescs)
+ continue;
+
+ String nfcname;
+ Value nfcdesc;
+ BOOST_FOREACH(tie(nfcname, nfcdesc), notificationDescs) {
+ stringstream namebuf;
+ namebuf << GetName() << "-" << nfcname;
+ String name = namebuf.str();
+
+ ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(item->GetDebugInfo());
+ builder->SetType("Notification");
+ builder->SetName(name);
+ builder->AddExpression("host_name", OperatorSet, GetHost()->GetName());
+ builder->AddExpression("service", OperatorSet, GetName());
+
+ CopyNotificationAttributes(this, builder);
+
+ if (nfcdesc.IsScalar()) {
+ builder->AddParent(nfcdesc);
+ } else if (nfcdesc.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr notification = nfcdesc;
+
+ String parent = notification->Get("notification");
+ if (parent.IsEmpty())
+ parent = nfcname;
+
+ builder->AddParent(parent);
+
+ CopyNotificationAttributes(notification, builder);
+ } else {
+ BOOST_THROW_EXCEPTION(invalid_argument("Notification description must be either a string or a dictionary."));
+ }
+
+ ConfigItem::Ptr notificationItem = builder->Compile();
+ notificationItem->Commit();
+
+ newNotifications->Set(name, notificationItem);
+ }
+ }
+
+ if (oldNotifications) {
+ ConfigItem::Ptr notification;
+ BOOST_FOREACH(tie(tuples::ignore, notification), oldNotifications) {
+ if (!notification)
+ continue;
+
+ if (!newNotifications->Contains(notification->GetName()))
+ notification->Unregister();
+ }
+ }
+
+ Set("slave_notifications", newNotifications);
+}
SetLastCheckResult(cr);
- // TODO(debug): remove this
- SendNotifications();
+ double now = Utility::GetTime();
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)) {
service->SetNextCheck(Utility::GetTime());
}
- // TODO: notify our child services/hosts that our state has changed
+ if (GetState() != StateOK)
+ TriggerDowntimes();
}
- if (GetState() != StateOK)
- TriggerDowntimes();
+ if (GetStateType() == StateTypeHard && (old_state != GetState() || old_stateType == StateTypeSoft)) {
+ SetLastHardStateChange(now);
+
+ /* Make sure the notification component sees the updated
+ * state/state_type attributes. */
+ DynamicObject::FlushTx();
+
+ RequestNotifications(NotificationStateChange);
+ }
}
ServiceState Service::StateFromString(const String& state)
else if (name == "comments")
Service::InvalidateCommentCache();
else if (name == "notifications")
- Service::InvalidateNotificationsCache();
+ UpdateSlaveNotifications();
}
void Service::BeginExecuteCheck(const function<void (void)>& callback)
static void ValidateCommentCache(void);
/* Notifications */
- void SendNotifications(void) const;
+ void RequestNotifications(NotificationType type) const;
+ void SendNotifications(NotificationType type) const;
static void InvalidateNotificationsCache(void);
static void ValidateNotificationsCache(void);
set<Notification::Ptr> GetNotifications(void) const;
+ void UpdateSlaveNotifications(void);
+
protected:
virtual void OnAttributeChanged(const String& name, const Value& oldValue);
BOOST_FOREACH(tie(tuples::ignore, object), DynamicType::GetByName("Endpoint")->GetObjects()) {
Endpoint::Ptr endpoint = dynamic_pointer_cast<Endpoint>(object);
/* don't forward messages between non-local endpoints */
- if (!sender->IsLocal() && !endpoint->IsLocal())
+ if ((sender && !sender->IsLocal()) && !endpoint->IsLocal())
continue;
if (endpoint->HasSubscription(method))