Commit 7fc51d19 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'health-check-route'

# Conflicts:
#	db/schema.rb
parents 74c69709 ad77ab03
...@@ -14,6 +14,7 @@ v 8.8.0 (unreleased) ...@@ -14,6 +14,7 @@ v 8.8.0 (unreleased)
- Reduce delay in destroying a project from 1-minute to immediately - Reduce delay in destroying a project from 1-minute to immediately
- Make build status canceled if any of the jobs was canceled and none failed - Make build status canceled if any of the jobs was canceled and none failed
- Upgrade Sidekiq to 4.1.2 - Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status
- Sanitize repo paths in new project error message - Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections - Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph. - Remove future dates from contribution calendar graph.
......
...@@ -332,3 +332,6 @@ gem 'oauth2', '~> 1.0.0' ...@@ -332,3 +332,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'
...@@ -402,6 +402,8 @@ GEM ...@@ -402,6 +402,8 @@ GEM
html2haml (>= 1.0.1) html2haml (>= 1.0.1)
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
highline (1.7.8) highline (1.7.8)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
...@@ -945,6 +947,7 @@ DEPENDENCIES ...@@ -945,6 +947,7 @@ DEPENDENCIES
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0) haml-rails (~> 0.9.0)
health_check (~> 1.5.1)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
......
...@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path redirect_to admin_runners_path
end end
def reset_health_check_token
@application_setting.reset_health_check_access_token!
flash[:notice] = 'New health check access token has been generated!'
redirect_to :back
end
def clear_repository_check_states def clear_repository_check_states
RepositoryCheck::ClearWorker.perform_async RepositoryCheck::ClearWorker.perform_async
......
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks('standard')
end
end
class HealthCheckController < HealthCheck::HealthCheckController
before_action :validate_health_check_access!
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
end
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :runners_registration_token add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
CACHE_KEY = 'application_setting.last' CACHE_KEY = 'application_setting.last'
...@@ -81,6 +82,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -81,6 +82,7 @@ class ApplicationSetting < ActiveRecord::Base
end end
before_save :ensure_runners_registration_token before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
after_commit do after_commit do
Rails.cache.write(CACHE_KEY, self) Rails.cache.write(CACHE_KEY, self)
...@@ -145,4 +147,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -145,4 +147,8 @@ class ApplicationSetting < ActiveRecord::Base
def runners_registration_token def runners_registration_token
ensure_runners_registration_token! ensure_runners_registration_token!
end end
def health_check_access_token
ensure_health_check_access_token!
end
end end
- page_title "Health Check"
%h3.page-title
Health Check
.bs-callout.clearfix
.pull-left
%p
Access token is
%code#health-check-token= current_application_settings.health_check_access_token
= button_to reset_health_check_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: 'Are you sure you want to reset the health check token?' } do
= icon('refresh')
Reset health check access token
%p.light
Health information can be reteived as plain text, json, or xml using:
%ul
%li
%code= health_check_url(token: current_application_settings.health_check_access_token)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
%p.light
You can also ask for the status of specific services:
%ul
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
%li
%code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
%hr
.panel.panel-default
.panel-heading
Current Status:
- if @errors.blank?
= icon('circle', class: 'cgreen')
Healthy
- else
= icon('warning', class: 'cred')
Unhealthy
.panel-body
- if @errors.blank?
No Health Problems Detected
- else
= @errors
...@@ -41,6 +41,11 @@ ...@@ -41,6 +41,11 @@
= icon('file-text fw') = icon('file-text fw')
%span %span
Logs Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw')
%span
Health Check
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw') = icon('bullhorn fw')
......
HealthCheck.setup do |config|
config.standard_checks = ['database', 'migrations', 'cache']
end
...@@ -73,6 +73,9 @@ Rails.application.routes.draw do ...@@ -73,6 +73,9 @@ Rails.application.routes.draw do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end end
# Health check
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
# Enable Grack support # Enable Grack support
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
...@@ -254,6 +257,7 @@ Rails.application.routes.draw do ...@@ -254,6 +257,7 @@ Rails.application.routes.draw do
end end
resource :logs, only: [:show] resource :logs, only: [:show]
resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show]
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
...@@ -285,6 +289,7 @@ Rails.application.routes.draw do ...@@ -285,6 +289,7 @@ Rails.application.routes.draw do
resource :application_settings, only: [:show, :update] do resource :application_settings, only: [:show, :update] do
resources :services resources :services
put :reset_runners_token put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states put :clear_repository_check_states
end end
......
class AddHealthCheckAccessTokenToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :health_check_access_token, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160508194200) do ActiveRecord::Schema.define(version: 20160509201028) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -81,6 +81,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do ...@@ -81,6 +81,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
t.text "shared_runners_text" t.text "shared_runners_text"
t.integer "metrics_packet_size", default: 1 t.integer "metrics_packet_size", default: 1
t.text "disabled_oauth_sign_in_sources" t.text "disabled_oauth_sign_in_sources"
t.string "health_check_access_token"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
require 'spec_helper'
describe HealthCheckController do
let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
describe 'GET #index' do
context 'when services are up but NO access token' do
it 'returns a not found page' do
get :index
expect(response).to be_not_found
end
end
context 'when services are up and an access token is provided' do
it 'supports passing the token in the header' do
request.headers['TOKEN'] = token
get :index
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful plaintest response' do
get :index, token: token
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful json response' do
get :index, token: token, format: :json
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
it 'supports successful xml response' do
get :index, token: token, format: :xml
expect(response).to be_success
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
it 'supports successful responses for specific checks' do
get :index, token: token, checks: 'email', format: :json
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
end
context 'when a service is down but NO access token' do
it 'returns a not found page' do
get :index
expect(response).to be_not_found
end
end
context 'when a service is down and an access token is provided' do
before do
allow(HealthCheck::Utils).to receive(:process_checks).with('standard').and_return('The server is on fire')
allow(HealthCheck::Utils).to receive(:process_checks).with('email').and_return('Email is on fire')
end
it 'supports passing the token in the header' do
request.headers['TOKEN'] = token
get :index
expect(response.status).to eq(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure plaintest response' do
get :index, token: token
expect(response.status).to eq(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure json response' do
get :index, token: token, format: :json
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('The server is on fire')
end
it 'supports failure xml response' do
get :index, token: token, format: :xml
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be false
expect(xml_response['message']).to include('The server is on fire')
end
it 'supports failure responses for specific checks' do
get :index, token: token, checks: 'email', format: :json
expect(response.status).to eq(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
expect(json_response['message']).to include('Email is on fire')
end
end
end
end
require 'spec_helper'
feature "Admin Health Check", feature: true do
include WaitForAjax
before do
login_as :admin
end
describe '#show' do
before do
visit admin_health_check_path
end
it { page.has_text? 'Health Check' }
it { page.has_text? 'Health information can be reteived' }
it 'has a health check access token' do
token = current_application_settings.health_check_access_token
expect(page).to have_content("Access token is #{token}")
expect(page).to have_selector('#health-check-token', text: token)
end
describe 'reload access token', js: true do
it 'changes the access token' do
orig_token = current_application_settings.health_check_access_token
click_button 'Reset health check access token'
wait_for_ajax
expect(find('#health-check-token').text).not_to eq orig_token
end
end
end
context 'when services are up' do
before do
visit admin_health_check_path
end
it 'shows healthy status' do
expect(page).to have_content('Current Status: Healthy')
end
end
context 'when a service is down' do
before do
allow(HealthCheck::Utils).to receive(:process_checks).and_return('The server is on fire')
visit admin_health_check_path
end
it 'shows unhealthy status' do
expect(page).to have_content('Current Status: Unhealthy')
expect(page).to have_content('The server is on fire')
end
end
end
...@@ -118,3 +118,10 @@ describe Admin::DashboardController, "routing" do ...@@ -118,3 +118,10 @@ describe Admin::DashboardController, "routing" do
expect(get("/admin")).to route_to('admin/dashboard#index') expect(get("/admin")).to route_to('admin/dashboard#index')
end end
end end
# admin_health_check GET /admin/health_check(.:format) admin/health_check#show
describe Admin::HealthCheckController, "routing" do
it "to #show" do
expect(get("/admin/health_check")).to route_to('admin/health_check#show')
end
end
...@@ -276,3 +276,13 @@ describe "Groups", "routing" do ...@@ -276,3 +276,13 @@ describe "Groups", "routing" do
expect(get('/1')).to route_to('namespaces#show', id: '1') expect(get('/1')).to route_to('namespaces#show', id: '1')
end end
end end
describe HealthCheckController, 'routing' do
it 'to #index' do
expect(get('/health_check')).to route_to('health_check#index')
end
it 'also supports passing checks in the url' do
expect(get('/health_check/email')).to route_to('health_check#index', checks: 'email')
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment