| 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 | } |