]> granicus.if.org Git - icinga2/commitdiff
Implement the CompatLog type.
authorGunnar Beutner <gunnar.beutner@netways.de>
Tue, 19 Mar 2013 12:04:30 +0000 (13:04 +0100)
committerGunnar Beutner <gunnar.beutner@netways.de>
Tue, 19 Mar 2013 12:04:30 +0000 (13:04 +0100)
12 files changed:
Makefile.am
components/compat/Makefile.am
components/compat/compat-type.conf
components/compat/compatcomponent.cpp
components/compat/compatcomponent.h
components/compat/compatlog.cpp [new file with mode: 0644]
components/compat/compatlog.h [new file with mode: 0644]
lib/icinga/host.cpp
lib/icinga/host.h
lib/icinga/service-check.cpp
lib/icinga/service.cpp
lib/icinga/service.h

index ca8dc863cfee0aad618f860ba4d04dc2d88e4c63..8f692d116c8319d190b826fcd4e8a8b2d4cf41e8 100644 (file)
@@ -26,7 +26,7 @@ icinga2doc_DATA = \
        NEWS
 
 install-data-local:
-       $(MKDIR_P) ${localstatedir}/log/${PACKAGE}
+       $(MKDIR_P) ${localstatedir}/log/${PACKAGE}/compat/archives
        $(MKDIR_P) ${localstatedir}/cache/${PACKAGE}
        $(MKDIR_P) ${localstatedir}/lib/${PACKAGE}
        $(MKDIR_P) ${localstatedir}/run/${PACKAGE}
index 712938ff4cf132e416fe6f530b28bce37d59927d..70638ce99e98947d84c90173ef34b4deb0bd1802 100644 (file)
@@ -12,6 +12,8 @@ EXTRA_DIST = \
 libcompat_la_SOURCES = \
        compatcomponent.cpp \
        compatcomponent.h \
+       compatlog.cpp \
+       compatlog.h \
        compat-type.cpp
 
 libcompat_la_CPPFLAGS = \
index 56e1fd8ccfae16a9a745b92ce195a733cb46675d..cbffdac5d397d14bed2e10ad72a2447c3a0bb836 100644 (file)
 type CompatComponent {
        %attribute string "status_path",
        %attribute string "objects_path",
-       %attribute string "log_path",
        %attribute string "command_path"
 }
+
+type CompatLog {
+       %attribute string "path_prefix",
+       %attribute number "rotation_interval"
+}
index 368ae4c97705cc868f6b7ecb2ffd1ac8a50fdc0f..87fcdeeee6c2d3a731f376804bde60df1bf9d4cb 100644 (file)
@@ -89,20 +89,6 @@ String CompatComponent::GetObjectsPath(void) const
                return objectsPath;
 }
 
-/**
- * Retrieves the log path.
- *
- * @returns log path
- */
-String CompatComponent::GetLogPath(void) const
-{
-       Value logPath = m_LogPath;
-       if (logPath.IsEmpty())
-               return Application::GetLocalStateDir() + "/log/icinga2/compat";
-       else
-               return logPath;
-}
-
 /**
  * Retrieves the icinga.cmd path.
  *
@@ -122,7 +108,6 @@ CompatComponent::CompatComponent(const Dictionary::Ptr& serializedUpdate)
 {
        RegisterAttribute("status_path", Attribute_Config, &m_StatusPath);
        RegisterAttribute("objects_path", Attribute_Config, &m_ObjectsPath);
-       RegisterAttribute("log_path", Attribute_Config, &m_LogPath);
        RegisterAttribute("command_path", Attribute_Config, &m_CommandPath);
 }
 
index d90bfe9487a42e9d5dd81a249a28a4173c6ca31b..f8232b12dfa4aef62cc538ff5c32787adc737b0f 100644 (file)
@@ -50,7 +50,6 @@ public:
 private:
        Attribute<String> m_StatusPath;
        Attribute<String> m_ObjectsPath;
-       Attribute<String> m_LogPath;
        Attribute<String> m_CommandPath;
 
 #ifndef _WIN32
@@ -63,7 +62,6 @@ private:
 
        String GetStatusPath(void) const;
        String GetObjectsPath(void) const;
-       String GetLogPath(void) const;
        String GetCommandPath(void) const;
 
        void DumpDowntimes(std::ostream& fp, const Service::Ptr& owner, CompatObjectType type);
diff --git a/components/compat/compatlog.cpp b/components/compat/compatlog.cpp
new file mode 100644 (file)
index 0000000..0108162
--- /dev/null
@@ -0,0 +1,274 @@
+/******************************************************************************
+ * 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 "compat/compatlog.h"
+#include "icinga/checkresultmessage.h"
+#include "icinga/service.h"
+#include "icinga/macroprocessor.h"
+#include "base/dynamictype.h"
+#include "base/objectlock.h"
+#include "base/logger_fwd.h"
+#include "base/convert.h"
+#include "base/application.h"
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/foreach.hpp>
+
+using namespace icinga;
+
+REGISTER_TYPE(CompatLog);
+
+CompatLog::CompatLog(const Dictionary::Ptr& properties)
+       : DynamicObject(properties)
+{
+       RegisterAttribute("log_dir", Attribute_Config, &m_LogDir);
+       RegisterAttribute("rotation_interval", Attribute_Config, &m_RotationInterval);
+}
+
+CompatLog::~CompatLog(void)
+{
+}
+
+/**
+ * @threadsafety Always.
+ */
+void CompatLog::OnAttributeChanged(const String& name)
+{
+       ASSERT(!OwnsLock());
+
+       if (name == "rotation_interval") {
+               m_RotationTimer->SetInterval(GetRotationInterval());
+       }
+}
+
+/**
+ * @threadsafety Always.
+ */
+void CompatLog::Start(void)
+{
+       m_Endpoint = Endpoint::MakeEndpoint("compatlog_" + GetName(), false);
+       m_Endpoint->RegisterTopicHandler("checker::CheckResult",
+           boost::bind(&CompatLog::CheckResultRequestHandler, this, _3));
+
+       m_RotationTimer = boost::make_shared<Timer>();
+       m_RotationTimer->OnTimerExpired.connect(boost::bind(&CompatLog::RotationTimerHandler, this));
+       m_RotationTimer->SetInterval(GetRotationInterval());
+       m_RotationTimer->Start();
+
+       RotateFile();
+}
+
+/**
+ * @threadsafety Always.
+ */
+CompatLog::Ptr CompatLog::GetByName(const String& name)
+{
+       DynamicObject::Ptr configObject = DynamicObject::GetObject("CompatLog", name);
+
+       return dynamic_pointer_cast<CompatLog>(configObject);
+}
+
+/**
+ * @threadsafety Always.
+ */
+String CompatLog::GetLogDir(void) const
+{
+       if (!m_LogDir.IsEmpty())
+               return m_LogDir;
+       else
+               return Application::GetLocalStateDir() + "/log/icinga2/compat/";
+}
+
+/**
+ * @threadsafety Always.
+ */
+double CompatLog::GetRotationInterval(void) const
+{
+       if (!m_RotationInterval.IsEmpty())
+               return m_RotationInterval;
+       else
+               return 3600;
+}
+
+/**
+ * @threadsafety Always.
+ */
+void CompatLog::CheckResultRequestHandler(const RequestMessage& request)
+{
+       CheckResultMessage params;
+       if (!request.GetParams(&params))
+               return;
+
+       String svcname = params.GetService();
+       Service::Ptr service = Service::GetByName(svcname);
+
+       Host::Ptr host = service->GetHost();
+
+       if (!host)
+               return;
+
+       Dictionary::Ptr cr = params.GetCheckResult();
+       if (!cr)
+               return;
+
+       Dictionary::Ptr vars_after = cr->Get("vars_after");
+
+       long state_after = vars_after->Get("state");
+       long stateType_after = vars_after->Get("state_type");
+       long attempt_after = vars_after->Get("attempt");
+       bool reachable_after = vars_after->Get("reachable");
+       bool host_reachable_after = vars_after->Get("host_reachable");
+
+       Dictionary::Ptr vars_before = cr->Get("vars_before");
+
+       if (vars_before) {
+               long state_before = vars_before->Get("state");
+               long stateType_before = vars_before->Get("state_type");
+               long attempt_before = vars_before->Get("attempt");
+               bool reachable_before = vars_before->Get("reachable");
+
+               if (state_before == state_after && stateType_before == stateType_after &&
+                   attempt_before == attempt_after && reachable_before == reachable_after)
+                       return; /* Nothing changed, ignore this checkresult. */
+       }
+
+       std::ostringstream msgbuf;
+       msgbuf << "SERVICE ALERT: "
+              << host->GetName() << ";"
+              << service->GetShortName() << ";"
+              << Service::StateToString(static_cast<ServiceState>(state_after)) << ";"
+              << Service::StateTypeToString(static_cast<StateType>(stateType_after)) << ";"
+              << attempt_after << ";"
+              << "";
+
+       WriteLine(msgbuf.str());
+
+       if (service == host->GetHostCheckService()) {
+               std::ostringstream msgbuf;
+               msgbuf << "HOST ALERT: "
+                      << host->GetName() << ";"
+                      << Host::StateToString(Host::CalculateState(static_cast<ServiceState>(state_after), host_reachable_after)) << ";"
+                      << Service::StateTypeToString(static_cast<StateType>(stateType_after)) << ";"
+                      << attempt_after << ";"
+                      << "";
+
+               WriteLine(msgbuf.str());
+       }
+
+       Flush();
+}
+
+void CompatLog::WriteLine(const String& line)
+{
+       ASSERT(OwnsLock());
+
+       if (!m_OutputFile.good())
+               return;
+
+       m_OutputFile << "[" << (long)Utility::GetTime() << "] " << line << "\n";
+}
+
+void CompatLog::Flush(void)
+{
+       ASSERT(OwnsLock());
+
+       if (!m_OutputFile.good())
+               return;
+
+       m_OutputFile << std::flush;
+}
+
+/**
+ * @threadsafety Always.
+ */
+void CompatLog::RotateFile(void)
+{
+       ObjectLock olock(this);
+
+       String tempFile = GetLogDir() + "/icinga.log";
+
+       if (m_OutputFile.good()) {
+               m_OutputFile.close();
+
+               String finalFile = GetLogDir() + "/archives/icinga-" + Convert::ToString((long)Utility::GetTime()) + ".log";
+               (void) rename(tempFile.CStr(), finalFile.CStr());
+       }
+
+       m_OutputFile.open(tempFile.CStr());
+
+       if (!m_OutputFile.good()) {
+               Log(LogWarning, "icinga", "Could not open compat log file '" + tempFile + "' for writing. Log output will be lost.");
+
+               return;
+       }
+
+       WriteLine("LOG VERSION: 2.0");
+
+       BOOST_FOREACH(const DynamicObject::Ptr& object, DynamicType::GetObjects("Host")) {
+               Host::Ptr host = static_pointer_cast<Host>(object);
+
+               Service::Ptr hc = host->GetHostCheckService();
+
+               if (!hc)
+                       continue;
+
+               bool reachable = host->IsReachable();
+
+               ObjectLock olock(hc);
+
+               std::ostringstream msgbuf;
+               msgbuf << "HOST STATE: CURRENT;"
+                      << host->GetName() << ";"
+                      << Host::StateToString(Host::CalculateState(hc->GetState(), reachable)) << ";"
+                      << Service::StateTypeToString(hc->GetStateType()) << ";"
+                      << hc->GetCurrentCheckAttempt() << ";"
+                      << "";
+
+               WriteLine(msgbuf.str());
+       }
+
+       BOOST_FOREACH(const DynamicObject::Ptr& object, DynamicType::GetObjects("Service")) {
+               Service::Ptr service = static_pointer_cast<Service>(object);
+
+               Host::Ptr host = service->GetHost();
+
+               if (!host)
+                       continue;
+
+               std::ostringstream msgbuf;
+               msgbuf << "SERVICE STATE: CURRENT;"
+                      << host->GetName() << ";"
+                      << service->GetShortName() << ";"
+                      << Service::StateToString(service->GetState()) << ";"
+                      << Service::StateTypeToString(service->GetStateType()) << ";"
+                      << service->GetCurrentCheckAttempt() << ";"
+                      << "";
+
+               WriteLine(msgbuf.str());
+       }
+
+       Flush();
+}
+
+/**
+ * @threadsafety Always.
+ */
+void CompatLog::RotationTimerHandler(void)
+{
+       RotateFile();
+}
diff --git a/components/compat/compatlog.h b/components/compat/compatlog.h
new file mode 100644 (file)
index 0000000..db2310f
--- /dev/null
@@ -0,0 +1,74 @@
+/******************************************************************************
+ * 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 COMPATLOG_H
+#define COMPATLOG_H
+
+#include "icinga/i2-icinga.h"
+#include "remoting/endpoint.h"
+#include "base/dynamicobject.h"
+#include "base/timer.h"
+#include <fstream>
+
+namespace icinga
+{
+
+/**
+ * An Icinga compat log writer.
+ *
+ * @ingroup compat
+ */
+class I2_ICINGA_API CompatLog : public DynamicObject
+{
+public:
+       typedef shared_ptr<CompatLog> Ptr;
+       typedef weak_ptr<CompatLog> WeakPtr;
+
+       CompatLog(const Dictionary::Ptr& properties);
+       ~CompatLog(void);
+
+       static CompatLog::Ptr GetByName(const String& name);
+
+       String GetLogDir(void) const;
+       double GetRotationInterval(void) const;
+
+protected:
+       virtual void OnAttributeChanged(const String& name);
+       virtual void Start(void);
+
+private:
+       Attribute<String> m_LogDir;
+       Attribute<double> m_RotationInterval;
+
+       void WriteLine(const String& line);
+       void Flush(void);
+
+       Endpoint::Ptr m_Endpoint;
+       void CheckResultRequestHandler(const RequestMessage& request);
+
+       Timer::Ptr m_RotationTimer;
+       void RotationTimerHandler(void);
+
+       std::ofstream m_OutputFile;
+       void RotateFile(void);
+};
+
+}
+
+#endif /* COMPATLOG_H */
index 04abac9809a4f9a7e9360e5266e96d1e19f9e00a..5ad3460369121b5cb21df0f04ee8200a41097e2a 100644 (file)
@@ -516,17 +516,12 @@ std::set<Service::Ptr> Host::GetParentServices(void) const
        return parents;
 }
 
-HostState Host::GetState(void) const
+HostState Host::CalculateState(ServiceState state, bool reachable)
 {
-       if (!IsReachable())
+       if (!reachable)
                return HostUnreachable;
 
-       Service::Ptr hc = GetHostCheckService();
-
-       if (!hc)
-               return HostUp;
-
-       switch (hc->GetState()) {
+       switch (state) {
                case StateOK:
                case StateWarning:
                        return HostUp;
@@ -535,16 +530,6 @@ HostState Host::GetState(void) const
        }
 }
 
-StateType Host::GetStateType(void) const
-{
-       Service::Ptr hc = GetHostCheckService();
-
-       if (!hc)
-               return StateTypeHard;
-
-       return hc->GetStateType();
-}
-
 HostState Host::GetLastState(void) const
 {
        ASSERT(!OwnsLock());
@@ -576,7 +561,7 @@ StateType Host::GetLastStateType(void) const
        return hc->GetLastStateType();
 }
 
-String Host::HostStateToString(HostState state)
+String Host::StateToString(HostState state)
 {
        switch (state) {
                case HostUp:
@@ -611,13 +596,16 @@ Dictionary::Ptr Host::CalculateDynamicMacros(void) const
        if (hc) {
                ObjectLock olock(hc);
 
-               macros->Set("HOSTSTATE", HostStateToString(GetState()));
-               macros->Set("HOSTSTATEID", GetState());
+               ServiceState state = hc->GetState();
+               bool reachable = IsReachable();
+
+               macros->Set("HOSTSTATE", CalculateState(state, reachable));
+               macros->Set("HOSTSTATEID", state);
                macros->Set("HOSTSTATETYPE", Service::StateTypeToString(hc->GetStateType()));
                macros->Set("HOSTATTEMPT", hc->GetCurrentCheckAttempt());
                macros->Set("MAXHOSTATTEMPT", hc->GetMaxCheckAttempts());
 
-               macros->Set("LASTHOSTSTATE", HostStateToString(GetLastState()));
+               macros->Set("LASTHOSTSTATE", StateToString(GetLastState()));
                macros->Set("LASTHOSTSTATEID", GetLastState());
                macros->Set("LASTHOSTSTATETYPE", Service::StateTypeToString(GetLastStateType()));
                macros->Set("LASTHOSTSTATECHANGE", (long)hc->GetLastStateChange());
index adcb29c6d612496053e0f5cede09af7a3508d572..98c5271699a0b7b0ae0690d82379d104a104c33b 100644 (file)
@@ -42,6 +42,20 @@ enum HostState
        HostUnreachable = 2
 };
 
+/**
+ * The state of a service.
+ *
+ * @ingroup icinga
+ */
+enum ServiceState
+{
+       StateOK = 0,
+       StateWarning = 1,
+       StateCritical = 2,
+       StateUnknown = 3,
+       StateUncheckable = 4,
+};
+
 /**
  * The state type of a host or service.
  *
@@ -93,13 +107,12 @@ public:
        static void ValidateServiceDictionary(const ScriptTask::Ptr& task,
            const std::vector<icinga::Value>& arguments);
 
-       HostState GetState(void) const;
-       StateType GetStateType(void) const;
+       static HostState CalculateState(ServiceState state, bool reachable);
 
        HostState GetLastState(void) const;
        StateType GetLastStateType(void) const;
 
-       static String HostStateToString(HostState state);
+       static String StateToString(HostState state);
 
 protected:
        virtual void OnRegistrationCompleted(void);
index 44d8542c1694f569dcde6f2be7614cb2e818c49e..76c02bb77f0103616435f2cb9ba02a57b7e2e805 100644 (file)
@@ -267,6 +267,26 @@ StateType Service::GetLastStateType(void) const
        return static_cast<StateType>(ivalue);
 }
 
+/**
+ * @threadsafety Always.
+ */
+void Service::SetLastReachable(bool reachable)
+{
+       m_LastReachable = reachable;
+       Touch("last_reachable");
+}
+
+/**
+ * @threadsafety Always.
+ */
+bool Service::GetLastReachable(void) const
+{
+       if (m_LastReachable.IsEmpty())
+               return true;
+
+       return m_LastReachable;
+}
+
 /**
  * @threadsafety Always.
  */
@@ -391,11 +411,19 @@ void Service::ProcessCheckResult(const Dictionary::Ptr& cr)
 {
        bool reachable = IsReachable();
 
+       Host::Ptr host = GetHost();
+       bool host_reachable = true;
+
+       if (host)
+               host_reachable = host->IsReachable();
+
        ASSERT(!OwnsLock());
        ObjectLock olock(this);
 
+       Dictionary::Ptr old_cr = GetLastCheckResult();
        ServiceState old_state = GetState();
        StateType old_stateType = GetStateType();
+       long old_attempt = GetCurrentCheckAttempt();
        bool hardChange = false;
        bool recovery;
 
@@ -403,8 +431,9 @@ void Service::ProcessCheckResult(const Dictionary::Ptr& cr)
         * in case this was a passive check result. */
        SetLastState(old_state);
        SetLastStateType(old_stateType);
+       SetLastReachable(reachable);
 
-       long attempt = GetCurrentCheckAttempt();
+       long attempt;
 
        if (cr->Get("state") == StateOK) {
                if (old_state != StateOK && old_stateType == StateTypeHard)
@@ -419,13 +448,15 @@ void Service::ProcessCheckResult(const Dictionary::Ptr& cr)
                attempt = 1;
                recovery = true;
        } else {
-               if (attempt >= GetMaxCheckAttempts()) {
+               if (old_attempt >= GetMaxCheckAttempts()) {
                        SetStateType(StateTypeHard);
                        attempt = 1;
                        hardChange = true;
                } else if (GetStateType() == StateTypeSoft || GetState() == StateOK) {
                        SetStateType(StateTypeSoft);
-                       attempt++;
+                       attempt = old_attempt + 1;
+               } else {
+                       attempt = old_attempt;
                }
 
                recovery = false;
@@ -482,6 +513,18 @@ void Service::ProcessCheckResult(const Dictionary::Ptr& cr)
 
        olock.Unlock();
 
+       Dictionary::Ptr vars_after = boost::make_shared<Dictionary>();
+       vars_after->Set("state", GetState());
+       vars_after->Set("state_type", GetStateType());
+       vars_after->Set("attempt", GetCurrentCheckAttempt());
+       vars_after->Set("reachable", reachable);
+       vars_after->Set("host_reachable", host_reachable);
+
+       if (old_cr)
+               cr->Set("vars_before", old_cr->Get("vars_after"));
+
+       cr->Set("vars_after", vars_after);
+
        /* Update macros - these are used by event handlers and notifications. */
        cr->Set("macros", CalculateAllMacros(cr));
 
@@ -619,6 +662,8 @@ void Service::BeginExecuteCheck(const boost::function<void (void)>& callback)
                SetLastStateType(GetLastStateType());
        }
 
+       SetLastReachable(IsReachable());
+
        /* keep track of scheduling info in case the check type doesn't provide its own information */
        Dictionary::Ptr checkInfo = boost::make_shared<Dictionary>();
        checkInfo->Set("schedule_start", GetNextCheck());
index 6314030a20c0a925afc36c1787dbc6b7def95d88..1b14e8860933cd89b0d45944271ead205afb80f9 100644 (file)
@@ -55,6 +55,7 @@ Service::Service(const Dictionary::Ptr& serializedObject)
        RegisterAttribute("state_type", Attribute_Replicated, &m_StateType);
        RegisterAttribute("last_state", Attribute_Replicated, &m_LastState);
        RegisterAttribute("last_state_type", Attribute_Replicated, &m_LastStateType);
+       RegisterAttribute("last_reachable", Attribute_Replicated, &m_LastReachable);
        RegisterAttribute("last_result", Attribute_Replicated, &m_LastResult);
        RegisterAttribute("last_state_change", Attribute_Replicated, &m_LastStateChange);
        RegisterAttribute("last_hard_state_change", Attribute_Replicated, &m_LastHardStateChange);
index b6052c43c1c72916bbacb2fc862419ddd1390cb1..b801081733a17f31763e8b1ea54a409aca824c96 100644 (file)
 namespace icinga
 {
 
-/**
- * The state of a service.
- *
- * @ingroup icinga
- */
-enum ServiceState
-{
-       StateOK,
-       StateWarning,
-       StateCritical,
-       StateUnknown,
-       StateUncheckable,
-};
-
 /**
  * The acknowledgement type of a service.
  *
@@ -158,6 +144,9 @@ public:
        void SetLastHardStateChange(double ts);
        double GetLastHardStateChange(void) const;
 
+       void SetLastReachable(bool reachable);
+       bool GetLastReachable(void) const;
+
        bool GetEnableActiveChecks(void) const;
        void SetEnableActiveChecks(bool enabled);
 
@@ -284,6 +273,7 @@ private:
        Attribute<long> m_StateType;
        Attribute<long> m_LastState;
        Attribute<long> m_LastStateType;
+       Attribute<bool> m_LastReachable;
        Attribute<Dictionary::Ptr> m_LastResult;
        Attribute<double> m_LastStateChange;
        Attribute<double> m_LastHardStateChange;