/* The software in this package is distributed under the GNU General Public License version 2 (with a special exception described below). A copy of GNU General Public License (GPL) is included in this distribution, in the file COPYING.GPL. As a special exception, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other works to produce a work based on this file, this file does not by itself cause the resulting work to be covered by the GNU General Public License. However the source code for this file must still be made available in accordance with section (3) of the GNU General Public License. This exception does not invalidate any other reasons why a work based on this file might be covered by the GNU General Public License. */ #include "link/linkstatus.h" #include #include #include "dns/dnsmaster.h" #include "boost_assert_handler.h" using namespace std; using boost::posix_time::microsec_clock; using boost::posix_time::ptime; using I2n::Logger::GlobalLogger; //----------------------------------------------------------------------------- // LinkStatus //----------------------------------------------------------------------------- /** * @brief Creates a link status object. * * @param hosts_down_limit The maximum amount of different hosts that can be * down before the system take any action. * @param link_up_interval_in_sec The amount of time required to the link to * stay up before notify. * @param link_down_interval_in_sec The amount of time required to the link to * stay down before notify. * @param status_notifier_cmd The command used to notify about link status * changes. */ LinkStatus::LinkStatus( const int hosts_down_limit, const int link_up_interval_in_sec, const int link_down_interval_in_sec, const string &status_notifier_cmd ) : HostsDownLimit( hosts_down_limit ), HostsDownList(), LinkUpIntervalInSec( link_up_interval_in_sec ), LinkDownIntervalInSec( link_down_interval_in_sec ), CurrentLinkStatus( Status_Down ), CurrentNotificationStatus( NotificationStatus_NotReported ), TimeLinkStatusChanged( microsec_clock::universal_time() ), StatusNotifierCmd( new StatusNotifierCommand( status_notifier_cmd ) ) { BOOST_ASSERT( 0 <= hosts_down_limit ); BOOST_ASSERT( 0 <= link_up_interval_in_sec ); BOOST_ASSERT( 0 <= link_down_interval_in_sec ); } LinkStatus::~LinkStatus() { } std::string LinkStatus::log_prefix() const { stringstream temp; temp << "Status (" << HostsDownList.size() << " down, " << "limit=" << HostsDownLimit << ", "; if ( can_report_link_status() ) { ptime now = microsec_clock::universal_time(); long seconds_missing; if ( CurrentLinkStatus == Status_Down ) { seconds_missing = LinkDownIntervalInSec - (now - TimeLinkStatusChanged).total_seconds(); temp << "notify down "; } else // (assume CurrentLinkStatus == Status_Up) { seconds_missing = LinkUpIntervalInSec - (now - TimeLinkStatusChanged).total_seconds(); temp << "notify up "; } if (seconds_missing < 0) temp << "now"; else temp << "in " << seconds_missing << "s"; } else temp << "no notify"; temp << "): "; return temp.str(); } /** * @brief Notify the system that a given host is up. The object takes an * appropriated action to deal with that. * Note: this object does not resolves IPs, thus you have to send the same host * address in order to the object to consider the same host. * * @param host_address the DNS/IP address of the host that is up. */ void LinkStatus::notify_host_up( const string &host_address ) { BOOST_ASSERT( !host_address.empty() ); bool has_changed = add_host_up( host_address ); if ( !exceeded_host_down_limit() ) { notify_link_up(); } if (has_changed) GlobalLogger.notice() << log_prefix() << "now up again is " << DnsMaster::get_cname_chain_str(host_address) << endl; else // less important so log only at info level GlobalLogger.info() << log_prefix() << "still up is " << DnsMaster::get_cname_chain_str(host_address) << endl; // removed from the list? BOOST_ASSERT( HostsDownList.count( host_address ) == 0 ); } //lint !e1788 /** * @brief Notify the system that a given host is down. The object takes an * appropriated action to deal with that. * Note: this object does not resolves IPs, thus you have to send the same host * address in order to the object to consider the same host. * * @param host_address The DNS/IP address of the host that is down. */ void LinkStatus::notify_host_down( const string &host_address ) { BOOST_ASSERT( !host_address.empty() ); add_host_down( host_address ); if ( exceeded_host_down_limit() ) { notify_link_down(); } // report this always at notice level GlobalLogger.notice() << log_prefix() << "down is " << DnsMaster::get_cname_chain_str(host_address) << endl; // inserted in the list? BOOST_ASSERT( HostsDownList.count( host_address ) == 1 ); } //lint !e1788 // returns true if this did change something (i.e. host had been down) bool LinkStatus::add_host_up( const string &host_address ) { if ( HostsDownList.count( host_address ) > 0 ) { size_t erased_host_count = HostsDownList.erase( host_address ); BOOST_ASSERT( erased_host_count == 1 ); return true; } else return false; } void LinkStatus::add_host_down( const string &host_address ) { (void) HostsDownList.insert( host_address ); } bool LinkStatus::exceeded_host_down_limit() const { int host_down_count = static_cast( HostsDownList.size() ); return ( host_down_count > HostsDownLimit ); } void LinkStatus::notify_link_up() { set_link_status( Status_Up ); // report the link status only if: it is up longer than a configured amount // of time, and if we haven't reported the new status yet if ( is_link_up_enough_time() && can_report_link_status() ) { BOOST_ASSERT( CurrentLinkStatus == Status_Up ); BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported ); StatusNotifierCmd->set_token_value( StatusNotifierCommand::StatusToken, "up" ); //lint !e534 GlobalLogger.notice() << log_prefix() << "report link up" << endl; bool executed = StatusNotifierCmd->execute(); if ( executed ) { CurrentNotificationStatus = NotificationStatus_Reported; LastReportedStatus = CurrentLinkStatus; } } } void LinkStatus::notify_link_down() { set_link_status( Status_Down ); // report the link status only if: it is down longer than a configured amount // of time, and if we haven't reported the new status yet if ( is_link_down_enough_time() && can_report_link_status() ) { BOOST_ASSERT( CurrentLinkStatus == Status_Down ); BOOST_ASSERT( CurrentNotificationStatus == NotificationStatus_NotReported ); GlobalLogger.notice() << log_prefix() << "report link down" << endl; StatusNotifierCmd->set_token_value( StatusNotifierCommand::StatusToken, "down" ); //lint !e534 bool executed = StatusNotifierCmd->execute(); if ( executed ) { CurrentNotificationStatus = NotificationStatus_Reported; LastReportedStatus = CurrentLinkStatus; } } } bool LinkStatus::is_link_up_enough_time() const { if ( CurrentLinkStatus == Status_Up ) { ptime now = microsec_clock::universal_time(); long amount_time_link_is_up = (now - TimeLinkStatusChanged).total_seconds(); if ( amount_time_link_is_up >= LinkUpIntervalInSec ) { return true; } } return false; } bool LinkStatus::is_link_down_enough_time() const { if ( CurrentLinkStatus == Status_Down ) { ptime now = microsec_clock::universal_time(); long amount_time_link_is_down = (now - TimeLinkStatusChanged).total_seconds(); if ( amount_time_link_is_down >= LinkDownIntervalInSec ) { return true; } } return false; } /** * @brief determine if we should report the current link status * * checks if the current status has already been reported; does not take * LinkUp/DownInterval into account * * @returns true if status should be reported */ bool LinkStatus::can_report_link_status() const { return ( CurrentNotificationStatus == NotificationStatus_NotReported && LastReportedStatus != CurrentLinkStatus ); } void LinkStatus::set_link_status( const LinkStatus::Status new_link_status ) { // only reset the control flags if the link status has changed if ( new_link_status != CurrentLinkStatus ) { CurrentLinkStatus = new_link_status; TimeLinkStatusChanged = microsec_clock::universal_time(); // have to report the link status change CurrentNotificationStatus = NotificationStatus_NotReported; } }