app/controllers/status_controller.rb
# frozen_string_literal: true
# Controller for the status endpoint
class StatusController < ApiController
skip_authorization_check only: [:index]
# GET /status
# GET /status.json (for backwards compatibility)
# only returns json
def index
statuses = start_checks
timed_out = statuses.wait(10) == false
storage, redis, upload, database = statuses.value(0)
# check promise values contain healthy values for each check.
# is any promise was rejected then #value returns nil
status = [
!timed_out, statuses.fulfilled?,
storage&.fetch(:success, false), redis == 'PONG',
upload&.success?, upload&.fmap { |r| r.try(:data_provider).fetch(:error) == '' }&.value_or(false),
database
].all?
result = {
status: status ? 'good' : 'bad',
timed_out: timed_out,
database: safe_result(statuses, index: 3),
redis: safe_result(statuses, index: 1),
storage: safe_result(statuses, index: 0) { |v| v[:message] },
upload: safe_result(statuses, index: 2) { |v|
status = v.value_or(v.failure&.response&.fetch(:body))
case status
when SftpgoClient::ApiResponse
[status.message, status.error].compact.join('. ')
when SftpgoClient::ServicesStatus
error = status.data_provider[:error]
error.blank? ? 'Alive' : error
else
status.to_s.strip
end
}
}
render json: result, status: :ok
end
private
def start_checks
Concurrent::Promises::FactoryMethods.zip(
Concurrent::Promises::FactoryMethods.future {
# indicates if audio recording storage is available
AudioRecording.check_storage
},
Concurrent::Promises::FactoryMethods.future {
# can we ping redis?
BawWorkers::Config.redis_communicator.ping
},
Concurrent::Promises::FactoryMethods.future {
# can we ping upload service?
BawWorkers::Config.upload_communicator.service_status
},
Concurrent::Promises::FactoryMethods.future {
ActiveRecord::Base.connection_pool.with_connection do
ActiveRecord::Base.connection.active?
end
}
)
end
# Transform a promise into a safe string
# @param [Concurrent::Promises::Future] promise
def safe_result(promise, index:)
return 'unknown' if promise.pending?
# result returns a tuple (an array) of
# [fulfilled?, value, reason]
# In our case, the value and reasons are arrays of values because we're
# dealing with a series of zipped promises
fulfilled, values, reasons = promise.result(0)
return 'timed out' if fulfilled.nil?
value = values[index]
return "error: #{reasons[index]}" if value.nil?
begin
(block_given? ? yield(value) : value)
rescue StandardError => e
Rails.logger.error(e)
'error getting value'
end
end
end