defea529bc62587c675d556b7937895ada674d84
[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 "boost_assert_handler.h"
27
28 using namespace std;
29 using boost::posix_time::microsec_clock;
30 using boost::posix_time::ptime;
31 using I2n::Logger::GlobalLogger;
32
33 //-----------------------------------------------------------------------------
34 // LinkStatus
35 //-----------------------------------------------------------------------------
36
37 /**
38  * @brief Creates a link status object.
39  *
40  * @param hosts_down_limit The maximum amount of different hosts that can be
41  * down before the system take any action.
42  * @param link_up_interval_in_min The amount of time required to the link to
43  * stay up before notify.
44  * @param link_down_interval_in_min The amount of time required to the link to
45  * stay down before notify.
46  * @param status_notifier_cmd The command used to notify about link status
47  * changes.
48  */
49 LinkStatus::LinkStatus(
50         const int hosts_down_limit,
51         const int link_up_interval_in_min,
52         const int link_down_interval_in_min,
53         const string &status_notifier_cmd
54 ) :
55     HostsDownLimit( hosts_down_limit ),
56     HostsDownList(),
57     LinkUpIntervalInMin( link_up_interval_in_min ),
58     LinkDownIntervalInMin( link_down_interval_in_min ),
59     CurrentLinkStatus( Status_Down ),
60     CurrentNotificationStatus( NotificationStatus_NotReported ),
61     TimeLinkStatusChanged( microsec_clock::universal_time() ),
62     StatusNotifierCmd( new StatusNotifierCommand( status_notifier_cmd ) )
63 {
64     BOOST_ASSERT( 0 <= hosts_down_limit );
65     BOOST_ASSERT( 0 <= link_up_interval_in_min );
66     BOOST_ASSERT( 0 <= link_down_interval_in_min );
67 }
68
69 LinkStatus::~LinkStatus()
70 {
71 }
72
73 /**
74  * @brief Notify the system that a given host is up. The object takes an
75  * appropriated action to deal with that.
76  * Note: this object does not resolves IPs, thus you have to send the same host
77  * address in order to the object to consider the same host.
78  *
79  * @param host_address the DNS/IP address of the host that is up.
80  */
81 void LinkStatus::notify_host_up( const string &host_address )
82 {
83     BOOST_ASSERT( !host_address.empty() );
84
85     bool has_changed = add_host_up( host_address );
86
87     if (has_changed)
88         GlobalLogger.notice() << "Status (" << HostsDownList.size()
89             << "/" << HostsDownLimit << " down): now up again is "
90             << host_address << endl;
91     else   // less important so log only at info level
92         GlobalLogger.info()  << "Status (" << HostsDownList.size()
93             << "/" << HostsDownLimit << " down): still up is "
94             << host_address << endl;
95
96     if ( !exceeded_host_down_limit() )
97     {
98         notify_link_up();
99     }
100
101     // removed from the list?
102     BOOST_ASSERT( HostsDownList.count( host_address ) == 0 );
103 } //lint !e1788
104
105 /**
106  * @brief Notify the system that a given host is down. The object takes an
107  * appropriated action to deal with that.
108  * Note: this object does not resolves IPs, thus you have to send the same host
109  * address in order to the object to consider the same host.
110  *
111  * @param host_address The DNS/IP address of the host that is down.
112  */
113 void LinkStatus::notify_host_down( const string &host_address )
114 {
115     BOOST_ASSERT( !host_address.empty() );
116
117     add_host_down( host_address );
118
119     // report this always at notice level
120     GlobalLogger.notice()  << "Status (" << HostsDownList.size()
121         << "/" << HostsDownLimit << " down): down is "
122         << host_address << endl;
123
124     if ( exceeded_host_down_limit() )
125     {
126         notify_link_down();
127     }
128
129     // inserted in the list?
130     BOOST_ASSERT( HostsDownList.count( host_address ) == 1 );
131 } //lint !e1788
132
133 // returns true if this did change something (i.e. host had been down)
134 bool LinkStatus::add_host_up( const string &host_address )
135 {
136     if ( HostsDownList.count( host_address ) > 0 )
137     {
138         size_t erased_host_count = HostsDownList.erase( host_address );
139
140         BOOST_ASSERT( erased_host_count == 1 );
141
142         return true;
143     }
144     else
145         return false;
146
147 }
148
149 void LinkStatus::add_host_down( const string &host_address )
150 {
151     (void) HostsDownList.insert( host_address );
152 }
153
154 bool LinkStatus::exceeded_host_down_limit() const
155 {
156     int host_down_count = static_cast<int>( HostsDownList.size() );
157
158     return ( host_down_count > HostsDownLimit );
159 }
160
161 void LinkStatus::notify_link_up()
162 {
163     set_link_status( Status_Up );
164
165     // report the link status only if: it is up longer than a configured amount
166     // of time, and if we haven't reported the new status yet
167     if ( is_link_up_enough_time() && can_report_link_status() )
168     {
169         BOOST_ASSERT( CurrentLinkStatus == Status_Up );
170         BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported );
171
172         StatusNotifierCmd->set_token_value(
173                 StatusNotifierCommand::StatusToken,
174                 "up"
175         );                                                                          //lint !e534
176
177         GlobalLogger.notice()  << "Status (" << HostsDownList.size()
178             << "/" << HostsDownLimit << " down): report link up" << endl;
179         bool executed = StatusNotifierCmd->execute();
180
181         if ( executed )
182         {
183             CurrentNotificationStatus = NotificationStatus_Reported;
184         }
185     }
186 }
187
188 void LinkStatus::notify_link_down()
189 {
190     set_link_status( Status_Down );
191
192     // report the link status only if: it is down longer than a configured amount
193     // of time, and if we haven't reported the new status yet
194     if ( is_link_down_enough_time() && can_report_link_status() )
195     {
196         BOOST_ASSERT( CurrentLinkStatus == Status_Down );
197         BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported );
198
199         GlobalLogger.notice()  << "Status (" << HostsDownList.size()
200             << "/" << HostsDownLimit << " down): report link down" << endl;
201         StatusNotifierCmd->set_token_value(
202                 StatusNotifierCommand::StatusToken,
203                 "down"
204         );                                                                              //lint !e534
205
206         bool executed = StatusNotifierCmd->execute();
207
208         if ( executed )
209         {
210             CurrentNotificationStatus = NotificationStatus_Reported;
211         }
212     }
213 }
214
215 bool LinkStatus::is_link_up_enough_time() const
216 {
217     if ( CurrentLinkStatus == Status_Up )
218     {
219         ptime now = microsec_clock::universal_time();
220         long amount_time_link_is_up = (now - TimeLinkStatusChanged).total_seconds();
221         long link_up_interval_in_sec = LinkUpIntervalInMin * 60;
222
223         if ( amount_time_link_is_up >= link_up_interval_in_sec )
224         {
225             return true;
226         }
227     }
228
229     return false;
230 }
231
232 bool LinkStatus::is_link_down_enough_time() const
233 {
234     if ( CurrentLinkStatus == Status_Down )
235     {
236         ptime now = microsec_clock::universal_time();
237         long amount_time_link_is_down = (now - TimeLinkStatusChanged).total_seconds();
238         long link_down_interval_in_sec = LinkDownIntervalInMin * 60;
239
240         if ( amount_time_link_is_down >= link_down_interval_in_sec )
241         {
242             return true;
243         }
244     }
245
246     return false;
247 }
248
249 bool LinkStatus::can_report_link_status() const
250 {
251     return ( CurrentNotificationStatus == NotificationStatus_NotReported );
252 }
253
254 void LinkStatus::set_link_status(
255         const LinkStatus::Status new_link_status
256 )
257 {
258     // only reset the control flags if the link status has changed
259     if ( new_link_status != CurrentLinkStatus )
260     {
261         CurrentLinkStatus = new_link_status;
262         TimeLinkStatusChanged = microsec_clock::universal_time();
263
264         // have to report the link status change
265         CurrentNotificationStatus = NotificationStatus_NotReported;
266     }
267 }