# frozen_string_literal: true require 'spec_helper' RSpec.describe HealthController, feature_category: :database do include StubENV let(:token) { Gitlab::CurrentSettings.health_check_access_token } let(:whitelisted_ip) { '1.1.1.1' } let(:not_whitelisted_ip) { '2.2.2.2' } let(:params) { {} } let(:headers) { {} } before do allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip]) stub_storage_settings({}) # Hide the broken storage stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end shared_context 'endpoint querying database' do it 'does query database' do control_count = ActiveRecord::QueryRecorder.new { subject }.count expect(control_count).not_to be_zero end end shared_context 'endpoint not querying database' do it 'does not query database' do control_count = ActiveRecord::QueryRecorder.new { subject }.count expect(control_count).to be_zero end end shared_context 'endpoint not found' do it 'responds with resource not found' do subject expect(response).to have_gitlab_http_status(:not_found) end end describe 'GET /-/health' do subject { get '/-/health', params: params, headers: headers } shared_context 'endpoint responding with health data' do it 'responds with health checks data' do subject expect(response).to have_gitlab_http_status(:ok) expect(response.body).to eq('GitLab OK') end end context 'accessed from whitelisted ip' do before do stub_remote_addr(whitelisted_ip) end it_behaves_like 'endpoint responding with health data' it_behaves_like 'endpoint not querying database' end context 'accessed from not whitelisted ip' do before do stub_remote_addr(not_whitelisted_ip) end it_behaves_like 'endpoint not querying database' it_behaves_like 'endpoint not found' end end describe 'GET /-/readiness' do subject { get '/-/readiness', params: params, headers: headers } shared_context 'endpoint responding with readiness data' do context 'when requesting instance-checks' do context 'when Puma runs in Clustered mode' do before do allow(Gitlab::Runtime).to receive(:puma_in_clustered_mode?).and_return(true) end it 'responds with readiness checks data' do expect(Gitlab::HealthChecks::MasterCheck).to receive(:check) { true } subject expect(json_response).to include({ 'status' => 'ok' }) expect(json_response['master_check']).to contain_exactly({ 'status' => 'ok' }) end it 'responds with readiness checks data when a failure happens' do expect(Gitlab::HealthChecks::MasterCheck).to receive(:check) { false } subject expect(json_response).to include({ 'status' => 'failed' }) expect(json_response['master_check']).to contain_exactly( { 'status' => 'failed', 'message' => 'unexpected Master check result: false' }) expect(response).to have_gitlab_http_status(:service_unavailable) expect(response.headers['X-GitLab-Custom-Error']).to eq(1) end end context 'when Puma runs in Single mode' do before do allow(Gitlab::Runtime).to receive(:puma_in_clustered_mode?).and_return(false) end it 'does not invoke MasterCheck, succeedes' do expect(Gitlab::HealthChecks::MasterCheck).not_to receive(:check) { true } subject expect(json_response).to eq('status' => 'ok') end end end context 'when requesting all checks' do shared_context 'endpoint responding with readiness data for all checks' do before do params.merge!(all: true) end it 'responds with readiness checks data' do subject expect(json_response['db_check']).to contain_exactly({ 'status' => 'ok' }) expect(json_response['cache_check']).to contain_exactly({ 'status' => 'ok' }) expect(json_response['queues_check']).to contain_exactly({ 'status' => 'ok' }) expect(json_response['shared_state_check']).to contain_exactly({ 'status' => 'ok' }) expect(json_response['gitaly_check']).to contain_exactly( { 'status' => 'ok', 'labels' => { 'shard' => 'default' } }) end it 'responds with readiness checks data when a failure happens' do allow(Gitlab::HealthChecks::Redis::SharedStateCheck).to receive(:readiness).and_return( Gitlab::HealthChecks::Result.new('shared_state_check', false, "check error")) subject expect(json_response['cache_check']).to contain_exactly({ 'status' => 'ok' }) expect(json_response['shared_state_check']).to contain_exactly( { 'status' => 'failed', 'message' => 'check error' }) expect(response).to have_gitlab_http_status(:service_unavailable) expect(response.headers['X-GitLab-Custom-Error']).to eq(1) end it 'checks all redis instances' do expected_redis_checks = Gitlab::Redis::ALL_CLASSES.map do |redis| { "#{redis.store_name.underscore}_check" => [{ 'status' => 'ok' }] } end subject expect(json_response).to include(*expected_redis_checks) end context 'when DB is not accessible and connection raises an exception' do before do expect(Gitlab::HealthChecks::DbCheck) .to receive(:readiness) .and_raise(PG::ConnectionBad, 'could not connect to server') end it 'responds with 500 including the exception info' do subject expect(response).to have_gitlab_http_status(:internal_server_error) expect(response.headers['X-GitLab-Custom-Error']).to eq(1) expect(json_response).to eq( { 'status' => 'failed', 'message' => 'PG::ConnectionBad : could not connect to server' }) end end context 'when any exception happens during the probing' do before do expect(Gitlab::HealthChecks::Redis::CacheCheck) .to receive(:readiness) .and_raise(::Redis::CannotConnectError, 'Redis down') end it 'responds with 500 including the exception info' do subject expect(response).to have_gitlab_http_status(:internal_server_error) expect(response.headers['X-GitLab-Custom-Error']).to eq(1) expect(json_response).to eq( { 'status' => 'failed', 'message' => 'Redis::CannotConnectError : Redis down' }) end end end context 'when Puma runs in Clustered mode' do before do allow(Gitlab::Runtime).to receive(:puma_in_clustered_mode?).and_return(true) end it_behaves_like 'endpoint responding with readiness data for all checks' end context 'when Puma runs in Single mode' do before do allow(Gitlab::Runtime).to receive(:puma_in_clustered_mode?).and_return(false) end it_behaves_like 'endpoint responding with readiness data for all checks' end end end context 'accessed from whitelisted ip' do before do stub_remote_addr(whitelisted_ip) end it_behaves_like 'endpoint not querying database' it_behaves_like 'endpoint responding with readiness data' context 'when requesting all checks' do before do params.merge!(all: true) end it_behaves_like 'endpoint querying database' end end context 'accessed from not whitelisted ip' do before do stub_remote_addr(not_whitelisted_ip) end it_behaves_like 'endpoint not querying database' it_behaves_like 'endpoint not found' end context 'accessed with valid token' do context 'token passed in request header' do let(:headers) { { TOKEN: token } } it_behaves_like 'endpoint responding with readiness data' it_behaves_like 'endpoint querying database' end context 'token passed as URL param' do let(:params) { { token: token } } it_behaves_like 'endpoint responding with readiness data' it_behaves_like 'endpoint querying database' end end end describe 'GET /-/liveness' do subject { get '/-/liveness', params: params, headers: headers } shared_context 'endpoint responding with liveness data' do it 'responds with liveness checks data' do subject expect(json_response).to eq('status' => 'ok') end end context 'accessed from whitelisted ip' do before do stub_remote_addr(whitelisted_ip) end it_behaves_like 'endpoint not querying database' it_behaves_like 'endpoint responding with liveness data' end context 'accessed from not whitelisted ip' do before do stub_remote_addr(not_whitelisted_ip) end it_behaves_like 'endpoint not querying database' it_behaves_like 'endpoint not found' context 'accessed with valid token' do context 'token passed in request header' do let(:headers) { { TOKEN: token } } it_behaves_like 'endpoint responding with liveness data' it_behaves_like 'endpoint querying database' end context 'token passed as URL param' do let(:params) { { token: token } } it_behaves_like 'endpoint responding with liveness data' it_behaves_like 'endpoint querying database' end end end end def stub_remote_addr(ip) headers.merge!(REMOTE_ADDR: ip) end end