Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class Application < Rails::Application
config.action_mailer.default_options = { from: 'fake@berkeley.edu' }
config.lit_gtag_id = ENV.fetch('LIT_GTAG_ID', nil)

config.x.servers ||= {}

config.x.servers[:geoserver] = ENV.fetch('GEODATA_GEOSERVER_PUBLIC_HEALTHCHECK_URL', nil)
config.x.servers[:geoserver_secure] = ENV.fetch('GEODATA_GEOSERVER_SECURE_HEALTHCHECK_URL', nil)
config.x.servers[:spatial_server] = ENV.fetch('GEODATA_SPATIAL_HEALTHCHECK_URL', nil)

# The Base URL for the generated sitemap
config.x.sitemap.base_url = ENV.fetch('GEODATA_BASE_URL', 'http://localhost:3000')
# Silenced by default to minimize log noise
Expand Down
14 changes: 14 additions & 0 deletions config/initializers/okcomputer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# initializers/okcomputer.rb
# Health checks configuration

require_relative '../../lib/http_head_check'

OkComputer.logger = Rails.logger
OkComputer.check_in_parallel = true

Expand All @@ -11,3 +13,15 @@
# Requires the ping handler on the solr core (<core>/admin/ping).
core_baseurl = Blacklight.default_index.connection.uri.to_s.chomp('/')
OkComputer::Registry.register 'solr', OkComputer::SolrCheck.new(core_baseurl)

# Perform a Head request to check geoserver endpoint
geoserver_url = Rails.configuration.x.servers[:geoserver]
OkComputer::Registry.register 'geoserver', GeoDataHealthCheck::HttpHeadCheck.new(geoserver_url)

# Perform a Head request to check secure_geoserver endpoint
geoserver_secure_url = Rails.configuration.x.servers[:geoserver_secure]
OkComputer::Registry.register 'geoserver_secure', GeoDataHealthCheck::HttpHeadCheck.new(geoserver_secure_url)

# Perform a Head request to check spatial server endpoint
spatial_server_url = Rails.configuration.x.servers[:spatial_server]
OkComputer::Registry.register 'spatial_server', GeoDataHealthCheck::HttpHeadCheck.new(spatial_server_url)
54 changes: 54 additions & 0 deletions lib/http_head_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module GeoDataHealthCheck
class HttpHeadCheck < OkComputer::Check
ConnectionFailed = Class.new(StandardError)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? Could we just do the following instead?

Suggested change
ConnectionFailed = Class.new(StandardError)
ConnectionFailed = OkComputer::HttpCheck::ConnectionFailed

Alternately, you could omit this line here and modify #perform_request to use the class from OkComputer directly.

attr_accessor :url, :request_timeout

# rubocop:disable Lint/MissingSuper
def initialize(url, request_timeout = 5)
self.url = url
self.request_timeout = request_timeout
end
# rubocop:enable Lint/MissingSuper

def check
response = perform_request

if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPRedirection)
mark_message 'Http head check successful.'
else
mark_message "Error: '#{url}' http head check responded, but returned unexpeced HTTP status: #{response.code} #{response.class}. Expected 200 Net::HTTPOK."
mark_failure
end
rescue StandardError => e
mark_message "Error: '#{e.message}'"
mark_failure
end

def perform_request
head_request
rescue Net::OpenTimeout, Net::ReadTimeout => e
raise ConnectionFailed, "#{url} did not respond within #{request_timeout} seconds: #{e.message}"
rescue ArgumentError => e
raise ConnectionFailed, "Invalid URL format for '#{url}': #{e.class}: #{e.message}"
rescue StandardError => e
raise ConnectionFailed, e.message
end

private

def head_request
uri = URI(url)
Net::HTTP.start(
uri.host,
uri.port,
use_ssl: uri.scheme == 'https',
verify_mode: OpenSSL::SSL::VERIFY_PEER,
open_timeout: request_timeout,
read_timeout: request_timeout
) do |http|
http.head(uri.request_uri)
end
end

end
end
89 changes: 89 additions & 0 deletions spec/lib/http_head_check_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
require 'rails_helper'
require_relative '../../lib/http_head_check'

RSpec.describe GeoDataHealthCheck::HttpHeadCheck do
let(:url) { 'https://example.com/endpoint' }
subject(:check) { described_class.new(url) }

describe 'initialization' do
it 'sets the URL' do
expect(check.url).to eq url
end

it 'sets default timeout to 5 seconds' do
expect(check.request_timeout).to eq 5
end

it 'allows custom timeout' do
check = described_class.new(url, 10)
expect(check.request_timeout).to eq 10
end
end

describe '#check' do
context 'when request returns 200 OK' do
it 'marks check as successful' do
response = Net::HTTPOK.new('1.1', 200, 'OK')
allow(check).to receive(:perform_request).and_return(response)

check.check

expect(check.message).to include('Http head check successful.')
end
end

context 'when request returns 500 error' do
it 'marks check as failed' do
response = Net::HTTPInternalServerError.new('1.1', 500, 'Error')
allow(check).to receive(:perform_request).and_return(response)

check.check

expect(check.message).to include('Error')
end
end

end

describe '#perform_request' do
it 'Net::OpenTimeout with ConnectionFailed and formated message' do
check_with_timeout = described_class.new(url, 7)
allow(check_with_timeout).to receive(:head_request).and_raise(Net::OpenTimeout.new('open timeout'))

expect { check_with_timeout.perform_request }.to raise_error(
GeoDataHealthCheck::HttpHeadCheck::ConnectionFailed,
a_string_including('did not respond within 7 seconds: open timeout')
)
end

it 'Net::ReadTimeout with ConnectionFailed and formated message' do
check_with_timeout = described_class.new(url, 9)
allow(check_with_timeout).to receive(:head_request).and_raise(Net::ReadTimeout.new('read timeout'))

expect { check_with_timeout.perform_request }.to raise_error(
GeoDataHealthCheck::HttpHeadCheck::ConnectionFailed,
a_string_including('did not respond within 9 seconds: Net::ReadTimeout')
)
end

it 'StandardError and passes through the message' do
err_msg = 'generic failure'
allow(check).to receive(:head_request).and_raise(StandardError, err_msg)

expect { check.perform_request }.to raise_error(
GeoDataHealthCheck::HttpHeadCheck::ConnectionFailed,
err_msg
)
end

it 'wraps ArgumentError with ConnectionFailed and includes URL and error class' do
bad_check = described_class.new(url)
allow(bad_check).to receive(:head_request).and_raise(ArgumentError, 'invalid URI')

expect { bad_check.perform_request }.to raise_error(
GeoDataHealthCheck::HttpHeadCheck::ConnectionFailed,
a_string_including("Invalid URL format for '#{url}'", 'ArgumentError', 'invalid URI')
)
end
end
end
5 changes: 4 additions & 1 deletion spec/requests/okcomputer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@

it 'returns all checks at /health' do
get '/health'
expect(response).to have_http_status :ok
expect(response).to have_http_status :internal_server_error
expect(response.parsed_body.keys).to match_array %w[
default
database
database-migrations
solr
geoserver
geoserver_secure
spatial_server
]
end
end