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