prevent pingcheck from repeating report of same status; show notification status...
[pingcheck] / src / link / linkstatus.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 #include "link/linkstatus.h"
21
22 #include <iostream>
23
24 #include <logfunc.hpp>
25
26 #include "dns/dnsmaster.h"
27
28 #include "boost_assert_handler.h"
29
30 using namespace std;
31 using boost::posix_time::microsec_clock;
32 using boost::posix_time::ptime;
33 using I2n::Logger::GlobalLogger;
34
35 //-----------------------------------------------------------------------------
36 // LinkStatus
37 //-----------------------------------------------------------------------------
38
39 /**
40  * @brief Creates a link status object.
41  *
42  * @param hosts_down_limit The maximum amount of different hosts that can be
43  * down before the system take any action.
44  * @param link_up_interval_in_sec The amount of time required to the link to
45  * stay up before notify.
46  * @param link_down_interval_in_sec The amount of time required to the link to
47  * stay down before notify.
48  * @param status_notifier_cmd The command used to notify about link status
49  * changes.
50  */
51 LinkStatus::LinkStatus(
52         const int hosts_down_limit,
53         const int link_up_interval_in_sec,
54         const int link_down_interval_in_sec,
55         const string &status_notifier_cmd
56 ) :
57     HostsDownLimit( hosts_down_limit ),
58     HostsDownList(),
59     LinkUpIntervalInSec( link_up_interval_in_sec ),
60     LinkDownIntervalInSec( link_down_interval_in_sec ),
61     CurrentLinkStatus( Status_Down ),
62     CurrentNotificationStatus( NotificationStatus_NotReported ),
63     TimeLinkStatusChanged( microsec_clock::universal_time() ),
64     StatusNotifierCmd( new StatusNotifierCommand( status_notifier_cmd ) )
65 {
66     BOOST_ASSERT( 0 <= hosts_down_limit );
67     BOOST_ASSERT( 0 <= link_up_interval_in_sec );
68     BOOST_ASSERT( 0 <= link_down_interval_in_sec );
69 }
70
71 LinkStatus::~LinkStatus()
72 {
73 }
74
75 std::string LinkStatus::log_prefix() const
76 {
77     stringstream temp;
78     temp << "Status (" << HostsDownList.size() << " down, "
79          << "limit=" << HostsDownLimit << ", ";
80     if ( can_report_link_status() )
81     {
82         ptime now = microsec_clock::universal_time();
83         long seconds_missing;
84         if ( CurrentLinkStatus == Status_Down )
85         {
86             seconds_missing = LinkDownIntervalInSec
87                               - (now - TimeLinkStatusChanged).total_seconds();
88             temp << "notify down ";
89         }
90         else // (assume CurrentLinkStatus == Status_Up)
91         {
92             seconds_missing = LinkUpIntervalInSec
93                               - (now - TimeLinkStatusChanged).total_seconds();
94             temp << "notify up ";
95         }
96         if (seconds_missing < 0)
97             temp << "now";
98         else
99             temp << "in " << seconds_missing << "s";
100     }
101     else
102         temp << "no notify";
103     temp << "): ";
104     return temp.str();
105 }
106
107 /**
108  * @brief Notify the system that a given host is up. The object takes an
109  * appropriated action to deal with that.
110  * Note: this object does not resolves IPs, thus you have to send the same host
111  * address in order to the object to consider the same host.
112  *
113  * @param host_address the DNS/IP address of the host that is up.
114  */
115 void LinkStatus::notify_host_up( const string &host_address )
116 {
117     BOOST_ASSERT( !host_address.empty() );
118
119     bool has_changed = add_host_up( host_address );
120
121     if ( !exceeded_host_down_limit() )
122     {
123         notify_link_up();
124     }
125
126     if (has_changed)
127         GlobalLogger.notice() << log_prefix() << "now up again is "
128             << DnsMaster::get_cname_chain_str(host_address) << endl;
129     else   // less important so log only at info level
130         GlobalLogger.info() << log_prefix() << "still up is "
131             << DnsMaster::get_cname_chain_str(host_address) << endl;
132
133     // removed from the list?
134     BOOST_ASSERT( HostsDownList.count( host_address ) == 0 );
135 } //lint !e1788
136
137 /**
138  * @brief Notify the system that a given host is down. The object takes an
139  * appropriated action to deal with that.
140  * Note: this object does not resolves IPs, thus you have to send the same host
141  * address in order to the object to consider the same host.
142  *
143  * @param host_address The DNS/IP address of the host that is down.
144  */
145 void LinkStatus::notify_host_down( const string &host_address )
146 {
147     BOOST_ASSERT( !host_address.empty() );
148
149     add_host_down( host_address );
150
151     if ( exceeded_host_down_limit() )
152     {
153         notify_link_down();
154     }
155
156     // report this always at notice level
157     GlobalLogger.notice() << log_prefix() << "down is "
158         << DnsMaster::get_cname_chain_str(host_address) << endl;
159
160     // inserted in the list?
161     BOOST_ASSERT( HostsDownList.count( host_address ) == 1 );
162 } //lint !e1788
163
164 // returns true if this did change something (i.e. host had been down)
165 bool LinkStatus::add_host_up( const string &host_address )
166 {
167     if ( HostsDownList.count( host_address ) > 0 )
168     {
169         size_t erased_host_count = HostsDownList.erase( host_address );
170
171         BOOST_ASSERT( erased_host_count == 1 );
172
173         return true;
174     }
175     else
176         return false;
177
178 }
179
180 void LinkStatus::add_host_down( const string &host_address )
181 {
182     (void) HostsDownList.insert( host_address );
183 }
184
185 bool LinkStatus::exceeded_host_down_limit() const
186 {
187     int host_down_count = static_cast<int>( HostsDownList.size() );
188
189     return ( host_down_count > HostsDownLimit );
190 }
191
192 void LinkStatus::notify_link_up()
193 {
194     set_link_status( Status_Up );
195
196     // report the link status only if: it is up longer than a configured amount
197     // of time, and if we haven't reported the new status yet
198     if ( is_link_up_enough_time() && can_report_link_status() )
199     {
200         BOOST_ASSERT( CurrentLinkStatus == Status_Up );
201         BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported );
202
203         StatusNotifierCmd->set_token_value(
204                 StatusNotifierCommand::StatusToken,
205                 "up"
206         );                                                                          //lint !e534
207
208         GlobalLogger.notice() << log_prefix() << "report link up" << endl;
209         bool executed = StatusNotifierCmd->execute();
210
211         if ( executed )
212         {
213             CurrentNotificationStatus = NotificationStatus_Reported;
214             LastReportedStatus = CurrentLinkStatus;
215         }
216     }
217 }
218
219 void LinkStatus::notify_link_down()
220 {
221     set_link_status( Status_Down );
222
223     // report the link status only if: it is down longer than a configured amount
224     // of time, and if we haven't reported the new status yet
225     if ( is_link_down_enough_time() && can_report_link_status() )
226     {
227         BOOST_ASSERT( CurrentLinkStatus == Status_Down );
228         BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported );
229
230         GlobalLogger.notice() << log_prefix() << "report link down" << endl;
231         StatusNotifierCmd->set_token_value(
232                 StatusNotifierCommand::StatusToken,
233                 "down"
234         );                                                                              //lint !e534
235
236         bool executed = StatusNotifierCmd->execute();
237
238         if ( executed )
239         {
240             CurrentNotificationStatus = NotificationStatus_Reported;
241             LastReportedStatus = CurrentLinkStatus;
242         }
243     }
244 }
245
246 bool LinkStatus::is_link_up_enough_time() const
247 {
248     if ( CurrentLinkStatus == Status_Up )
249     {
250         ptime now = microsec_clock::universal_time();
251         long amount_time_link_is_up = (now - TimeLinkStatusChanged).total_seconds();
252
253         if ( amount_time_link_is_up >= LinkUpIntervalInSec )
254         {
255             return true;
256         }
257     }
258
259     return false;
260 }
261
262 bool LinkStatus::is_link_down_enough_time() const
263 {
264     if ( CurrentLinkStatus == Status_Down )
265     {
266         ptime now = microsec_clock::universal_time();
267         long amount_time_link_is_down = (now - TimeLinkStatusChanged).total_seconds();
268
269         if ( amount_time_link_is_down >= LinkDownIntervalInSec )
270         {
271             return true;
272         }
273     }
274
275     return false;
276 }
277
278 /**
279  * @brief determine if we should report the current link status
280  *
281  * checks if the current status has already been reported; does not take
282  * LinkUp/DownInterval into account
283  *
284  * @returns true if status should be reported
285  */
286 bool LinkStatus::can_report_link_status() const
287 {
288     return ( CurrentNotificationStatus == NotificationStatus_NotReported
289                  && LastReportedStatus != CurrentLinkStatus );
290 }
291
292 void LinkStatus::set_link_status(
293         const LinkStatus::Status new_link_status
294 )
295 {
296     // only reset the control flags if the link status has changed
297     if ( new_link_status != CurrentLinkStatus )
298     {
299         CurrentLinkStatus = new_link_status;
300         TimeLinkStatusChanged = microsec_clock::universal_time();
301
302         // have to report the link status change
303         CurrentNotificationStatus = NotificationStatus_NotReported;
304     }
305 }