def query_whats_my_ip(options={})
      expected_ip = options[:expected_ip]
      raise ArgumentError.new("expected_ip is invalid") if expected_ip && !(expected_ip =~ IP_ADDRESS_REGEX)
      unanimous = options[:unanimous] || false
      host_name = options[:host_name] || DEFAULT_WHATS_MY_IP_HOST_NAME
      logger = options[:logger] || Logger.new(::RightScale::Platform::Shell::NULL_OUTPUT_NAME)
      timeout = options[:timeout] || DEFAULT_WHATS_MY_IP_TIMEOUT
      retry_delay = options[:retry_delay] || DEFAULT_WHATS_MY_IP_RETRY_DELAY

      if expected_ip
        logger.info("Waiting for IP=#{expected_ip}")
      else
        logger.info("Waiting for any IP to converge.")
      end

      # attempt to dig some hosts.
      hosts = `dig +short #{host_name}`.strip.split
      if hosts.empty?
        logger.info("No hosts to poll for IP from #{host_name}.")
      else
        # a little randomization avoids hitting the same hosts from each
        # instance since there is no guarantee that the hosts are returned in
        # random order.
        hosts = hosts.sort { (rand(2) * 2) - 1 }
        if logger.debug?
          message = ["Using these hosts to check the IP:"]
          hosts.each { |host| message << "  #{host}" }
          message << "-------------------------"
          logger.debug(message.join("\n"))
        end

        unanimity = hosts.count
        required_votes = unanimous ? unanimity : (1 + unanimity / 2)
        logger.info("Required votes = #{required_votes}/#{unanimity}")
        end_time = Time.now + timeout
        loop do
          reported_ips_to_voters = {}
          address_to_hosts = {}
          hosts.each do |host|
            address = `curl --max-time 1 -S -s http://#{host}/ip/mine`.strip
            logger.debug("Host=#{host} reports IP=#{address}")
            address_to_hosts[address] ||= []
            address_to_hosts[address] << host
            if expected_ip
              vote = (address_to_hosts[expected_ip] || []).count
              popular_address = expected_ip
            else
              popular_address = nil
              vote = 0
              address_to_hosts.each do |address, hosts|
                if hosts.count > vote
                  vote = hosts.count
                  popular_address = address
                end
              end
            end
            if vote >= required_votes
              logger.info("IP=#{popular_address} has the required vote count of #{required_votes}.")
              return popular_address
            end
          end

          # go around again, if possible.
          now_time = Time.now
          break if now_time >= end_time
          retry_delay = [retry_delay, end_time - now_time].min.to_i
          logger.debug("Sleeping for #{retry_delay} seconds...")
          sleep retry_delay
          retry_delay = [retry_delay * 2, 60].min  # a little backoff helps when launching thousands
        end
        logger.info("Never got the required vote count of #{required_votes}/#{unanimity} after #{timeout} seconds; public IP did not converge.")
        return nil
      end
    end
def query_whats_my_ip(options={})
      expected_ip = options[:expected_ip]
      raise ArgumentError.new("expected_ip is invalid") if expected_ip && !(expected_ip =~ IP_ADDRESS_REGEX)
      unanimous = options[:unanimous] || false
      host_name = options[:host_name] || DEFAULT_WHATS_MY_IP_HOST_NAME
      logger = options[:logger] || Logger.new(::RightScale::Platform::Shell::NULL_OUTPUT_NAME)
      timeout = options[:timeout] || DEFAULT_WHATS_MY_IP_TIMEOUT
      retry_delay = options[:retry_delay] || DEFAULT_WHATS_MY_IP_RETRY_DELAY

      if expected_ip
        logger.info("Waiting for IP=#{expected_ip}")
      else
        logger.info("Waiting for any IP to converge.")
      end

      # attempt to dig some hosts.
      hosts = `dig +short #{host_name}`.strip.split
      if hosts.empty?
        logger.info("No hosts to poll for IP from #{host_name}.")
      else
        # a little randomization avoids hitting the same hosts from each
        # instance since there is no guarantee that the hosts are returned in
        # random order.
        hosts = hosts.sort { (rand(2) * 2) - 1 }
        if logger.debug?
          message = ["Using these hosts to check the IP:"]
          hosts.each { |host| message << "  #{host}" }
          message << "-------------------------"
          logger.debug(message.join("\n"))
        end

        unanimity = hosts.count
        required_votes = unanimous ? unanimity : (1 + unanimity / 2)
        logger.info("Required votes = #{required_votes}/#{unanimity}")
        end_time = Time.now + timeout
        loop do
          reported_ips_to_voters = {}
          address_to_hosts = {}
          hosts.each do |host|
            address = `curl --max-time 1 -S -s http://#{host}/ip/mine`.strip
            logger.debug("Host=#{host} reports IP=#{address}")
            address_to_hosts[address] ||= []
            address_to_hosts[address] << host
            if expected_ip
              vote = (address_to_hosts[expected_ip] || []).count
              popular_address = expected_ip
            else
              popular_address = nil
              vote = 0
              address_to_hosts.each do |address, hosts|
                if hosts.count > vote
                  vote = hosts.count
                  popular_address = address
                end
              end
            end
            if vote >= required_votes
              logger.info("IP=#{popular_address} has the required vote count of #{required_votes}.")
              return popular_address
            end
          end

          # go around again, if possible.
          now_time = Time.now
          break if now_time >= end_time
          retry_delay = [retry_delay, end_time - now_time].min.to_i
          logger.debug("Sleeping for #{retry_delay} seconds...")
          sleep retry_delay
          retry_delay = [retry_delay * 2, 60].min  # a little backoff helps when launching thousands
        end
        logger.info("Never got the required vote count of #{required_votes}/#{unanimity} after #{timeout} seconds; public IP did not converge.")
        return nil
      end
    end
Queries a whats-my-ip service for the public IP address of this instance.
 can query either for an expected IP or for any IP which is voted by majority
 (or unanimously). this is no guarantee that an instance actually has a public
 IP individually assigned to it as a private cloud instance will still appear
 to have it's router's public IP as it's own address.

 === Parameters
 options[:expected_ip](String):: expected IP address or nil (no DNS names)
 options[:unanimous][TrueClass|FalseClass]:: true if vote must be unanimous, false for simple majority of all responders
 options[:host_name](String):: host name for whats-my-ip query or DEFAULT_WHATS_MY_IP_HOST_NAME
 options[:logger](Logger):: logger or defaults to null logger
 options[:timeout][Fixnum]:: timeout in seconds or DEFAULT_WHATS_MY_IP_TIMEOUT
 options[:retry_delay][Fixnum]:: retry delay in seconds or DEFAULT_WHATS_MY_IP_RETRY_DELAY

 === Return
 public_ip(String):: the consensus public IP for this instance or nil