pod.rb 3.5 KB
Newer Older
1
class Pod < ActiveRecord::Base
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  enum status: %i(
    unchecked
    no_errors
    dns_failed
    net_failed
    ssl_failed
    http_failed
    version_failed
    unknown_error
  )

  ERROR_MAP = {
    ConnectionTester::AddressFailure  => :dns_failed,
    ConnectionTester::DNSFailure      => :dns_failed,
    ConnectionTester::NetFailure      => :net_failed,
    ConnectionTester::SSLFailure      => :ssl_failed,
    ConnectionTester::HTTPFailure     => :http_failed,
    ConnectionTester::NodeInfoFailure => :version_failed
  }

22 23 24 25 26 27 28 29 30
  # this are only the most common errors, the rest will be +unknown_error+
  CURL_ERROR_MAP = {
    couldnt_resolve_host: :dns_failed,
    couldnt_connect:      :net_failed,
    operation_timedout:   :net_failed,
    ssl_cipher:           :ssl_failed,
    ssl_cacert:           :ssl_failed
  }.freeze

31 32 33 34
  DEFAULT_PORTS = [URI::HTTP::DEFAULT_PORT, URI::HTTPS::DEFAULT_PORT]

  has_many :people

35
  scope :check_failed, lambda {
36
    where(arel_table[:status].gt(Pod.statuses[:no_errors])).where.not(status: Pod.statuses[:version_failed])
37 38
  }

39 40
  validate :not_own_pod

41 42
  class << self
    def find_or_create_by(opts) # Rename this method to not override an AR method
43 44 45
      uri = URI.parse(opts.fetch(:url))
      port = DEFAULT_PORTS.include?(uri.port) ? nil : uri.port
      find_or_initialize_by(host: uri.host, port: port).tap do |pod|
46 47
        pod.ssl ||= (uri.scheme == "https")
        pod.save
48 49 50 51 52 53 54 55 56 57 58 59 60
      end
    end

    # don't consider a failed version reading to be fatal
    def offline_statuses
      [Pod.statuses[:dns_failed],
       Pod.statuses[:net_failed],
       Pod.statuses[:ssl_failed],
       Pod.statuses[:http_failed],
       Pod.statuses[:unknown_error]]
    end

    def check_all!
61
      Pod.find_in_batches(batch_size: 20) {|batch| batch.each(&:test_connection!) }
62 63 64 65 66 67 68
    end
  end

  def offline?
    Pod.offline_statuses.include?(Pod.statuses[status])
  end

69 70 71 72 73 74 75
  # a pod is active if it is online or was online less than 14 days ago
  def active?
    !offline? || offline_since.try {|date| date > DateTime.now.utc - 14.days }
  end

  def to_s
    "#{id}:#{host}"
76 77 78
  end

  def test_connection!
79
    result = ConnectionTester.check uri.to_s
80
    logger.debug "tested pod: '#{uri}' - #{result.inspect}"
81 82 83 84 85 86

    transaction do
      update_from_result(result)
    end
  end

87 88 89 90 91 92
  # @param path [String]
  # @return [String]
  def url_to(path)
    uri.tap {|uri| uri.path = path }.to_s
  end

93
  def update_offline_since
94
    if offline?
95
      self.offline_since ||= DateTime.now.utc
96 97 98
    else
      self.offline_since = nil
    end
99 100 101 102 103 104 105 106
  end

  private

  def update_from_result(result)
    self.status = status_from_result(result)
    update_offline_since
    logger.warn "OFFLINE #{result.failure_message}" if offline?
107 108 109 110 111 112 113 114

    attributes_from_result(result)
    touch(:checked_at)

    save
  end

  def attributes_from_result(result)
115
    self.ssl ||= result.ssl
116 117 118 119 120 121 122 123 124 125 126
    self.error = result.failure_message[0..254] if result.error?
    self.software = result.software_version[0..254] if result.software_version.present?
    self.response_time = result.rt
  end

  def status_from_result(result)
    if result.error?
      ERROR_MAP.fetch(result.error.class, :unknown_error)
    else
      :no_errors
    end
127
  end
128 129 130 131 132 133

  # @return [URI]
  def uri
    @uri ||= (ssl ? URI::HTTPS : URI::HTTP).build(host: host, port: port)
    @uri.dup
  end
134 135 136 137 138 139

  def not_own_pod
    pod_uri = AppConfig.pod_uri
    pod_port = DEFAULT_PORTS.include?(pod_uri.port) ? nil : pod_uri.port
    errors.add(:base, "own pod not allowed") if pod_uri.host == host && pod_port == port
  end
140
end