Skip to content

Commit

Permalink
Fixes #10782 - global host status
Browse files Browse the repository at this point in the history
Introduce new global host status that is composed of host substatuses.
Each substatus defines a mapping to the global one which can result in
three values
* OK
* WARN
* ERROR

Plugins can add their own substatuses. These are automatically
propagated also to API.

Thanks to Tomas Strachota who wrote the original code.
  • Loading branch information
ares committed Sep 4, 2015
1 parent 33d7500 commit df74227
Show file tree
Hide file tree
Showing 35 changed files with 867 additions and 175 deletions.
8 changes: 8 additions & 0 deletions app/assets/stylesheets/application.scss
Expand Up @@ -527,3 +527,11 @@ table {
.error-message{
padding-right: 10px;
}

i.glyphicon.host-status {
margin-right: 5px;
}

span.glyphicon.host-status {
top: 3px;
}
11 changes: 11 additions & 0 deletions app/assets/stylesheets/status-colors.scss
@@ -0,0 +1,11 @@
.status-ok {
color: #5CB85C;
}

.status-error {
color: #D9534F
}

.status-warn, .status-question {
color: #DB843D;
}
3 changes: 2 additions & 1 deletion app/controllers/api/v1/hosts_controller.rb
Expand Up @@ -125,7 +125,8 @@ def destroy
eos

def status
render :json => { :status => @host.host_status }.to_json if @host
Foreman::Deprecation.api_deprecation_warning('The /status route is deprecated, please use the new /status/configuration instead')
render :json => { :status => @host.get_status(HostStatus::ConfigurationStatus).to_label }.to_json if @host
end

private
Expand Down
31 changes: 27 additions & 4 deletions app/controllers/api/v2/hosts_controller.rb
Expand Up @@ -25,7 +25,10 @@ class HostsController < V2::BaseController
param_group :search_and_pagination, ::Api::V2::BaseController

def index
@hosts = resource_scope_for_index
@hosts = resource_scope_for_index.includes([ :host_statuses, :compute_resource, :hostgroup, :operatingsystem, :interfaces])
# SQL optimizations queries
@last_report_ids = Report.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
@last_reports = Report.where(:id => @last_report_ids.values)
end

api :GET, "/hosts/:id/", N_("Show a host")
Expand Down Expand Up @@ -109,7 +112,7 @@ def destroy
process_response @host.destroy
end

api :GET, "/hosts/:id/status", N_("Get status of host")
api :GET, "/hosts/:id/status", N_("Get configuration status of host")
param :id, :identifier_dottable, :required => true
description <<-eos
Return value may either be one of the following:
Expand All @@ -123,7 +126,27 @@ def destroy
eos

def status
render :json => { :status => @host.host_status }.to_json if @host
Foreman::Deprecation.api_deprecation_warning('The /status route is deprecated, please use the new /status/configuration instead')
render :json => { :status => @host.get_status(HostStatus::ConfigurationStatus).to_label }.to_json if @host
end

api :GET, "/hosts/:id/status/:type", N_("Get status of host")
param :id, :identifier_dottable, :required => true
param :type, [ HostStatus::Global ] + HostStatus.status_registry.to_a.map { |s| s.humanized_name }, :required => true, :desc => N_(<<-eos
status type, can be one of
* global
* configuration
* build
eos
)
description N_('Returns string representing a host status of a given type')
def get_status
case params[:type]
when 'global'
@status = @host.build_global_status
else
@status = @host.get_status(HostStatus.find_status_by_humanized_name(params[:type]))
end
end

api :GET, "/hosts/:id/vm_compute_attributes", N_("Get vm attributes of host")
Expand Down Expand Up @@ -245,7 +268,7 @@ def action_permission
:console
when 'disassociate'
:edit
when 'vm_compute_attributes'
when 'vm_compute_attributes', 'get_status'
:view
else
super
Expand Down
3 changes: 1 addition & 2 deletions app/controllers/application_controller.rb
Expand Up @@ -338,8 +338,7 @@ def check_empty_taxonomy
# If the user has a fact_filter then we need to include :fact_values
# We do not include most associations unless we are processing a html page
def included_associations(include = [])
include += [:hostgroup, :compute_resource, :operatingsystem, :environment, :model ]
include
include + [ :hostgroup, :compute_resource, :operatingsystem, :environment, :model, :host_statuses ]
end

def errors_hash(errors)
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/hosts_controller.rb
Expand Up @@ -40,7 +40,8 @@ def index(title = nil)
format.html do
@hosts = search.includes(included_associations).paginate(:page => params[:page])
# SQL optimizations queries
@last_reports = Report.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
@last_report_ids = Report.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
@last_reports = Report.where(:id => @last_report_ids.values)
# rendering index page for non index page requests (out of sync hosts etc)
@hostgroup_authorizer = Authorizer.new(User.current, :collection => @hosts.map(&:hostgroup_id).compact.uniq)
render :index if title and (@title = title)
Expand Down Expand Up @@ -194,7 +195,7 @@ def puppetrun
end

def review_before_build
@build = @host.build_status
@build = @host.build_status_checker
render :layout => false
end

Expand Down
97 changes: 59 additions & 38 deletions app/helpers/hosts_helper.rb
Expand Up @@ -62,7 +62,7 @@ def last_report_column(record)

def last_report_tooltip(record)
opts = { :rel => "twipsy" }
if @last_reports[record.id]
if @last_report_ids[record.id]
opts.merge!( "data-original-title" => _("View last report details"))
else
opts.merge!(:disabled => true, :class => "disabled", :onclick => 'return false')
Expand All @@ -72,44 +72,47 @@ def last_report_tooltip(record)
end

# method that reformat the hostname column by adding the status icons
def name_column(record)
label = record.host_status
case label
when "Pending Installation"
style ="label-info"
# TRANSLATORS: host's status: first character of "build"
short = s_("Build|B")
when "Alerts disabled"
style = "label-default"
# TRANSLATORS: host's status: first character of "disabled"
short = s_("Disabled|D")
when "No reports"
style = "label-default"
# TRANSLATORS: host's status: first character of "no reports"
short = s_("No reports|N")
when "Out of sync"
style = "label-warning"
# TRANSLATORS: host's status: first character of "sync" (out of sync)
short = s_("Sync|S")
when "Error"
style = "label-danger"
# TRANSLATORS: host's status: first character of "error"
short = s_("Error|E")
when "Active"
style = "label-info"
# TRANSLATORS: host's status: first character of "active"
short = s_("Active|A")
when "Pending"
style = "label-warning"
# TRANSLATORS: host's status: first character of "pending"
short = s_("Pending|P")
else
style = "label-success"
# TRANSLATORS: host's status: first character of "OK"
short = s_("OK|O")
def name_column(host)
style = host_global_status_icon_class_for_host(host)
tooltip = host.host_statuses.select(&:relevant?).sort_by(&:type).map { |status| "#{_(status.name)}: #{_(status.to_label)}" }.join(', ')

content = content_tag(:span, "", {:rel => "twipsy", :class => style, :"data-original-title" => tooltip} )
content += link_to(trunc_with_tooltip(" #{host}"), host_path(host))
content
end

def host_global_status_icon_class_for_host(host)
options = {}
options[:last_reports] = @last_reports unless @last_reports.nil?
host_global_status_icon_class(host.build_global_status(options).status)
end

def host_global_status_icon_class(status)
icon_class = case status
when HostStatus::Global::OK
'glyphicon-ok-sign'
when HostStatus::Global::WARN
'glyphicon-info-sign'
when HostStatus::Global::ERROR
'glyphicon-exclamation-sign'
else
'glyphicon-question-sign'
end

"host-status glyphicon #{icon_class} #{host_global_status_class(status)}"
end

def host_global_status_class(status)
case status
when HostStatus::Global::OK
'status-ok'
when HostStatus::Global::WARN
'status-warn'
when HostStatus::Global::ERROR
'status-error'
else
'status-question'
end
content_tag(:span, short, {:rel => "twipsy", :class => "label label-light " + style, :"data-original-title" => _(label)} ) +
link_to(trunc_with_tooltip(" #{record}"), host_path(record))
end

def days_ago(time)
Expand Down Expand Up @@ -247,7 +250,14 @@ def show_templates
end

def overview_fields(host)
global_status = host.build_global_status
fields = [
[_("Status"), content_tag(:i, ''.html_safe, :class => host_global_status_icon_class(global_status.status)) +
content_tag(:span, _(global_status.to_label), :class => host_global_status_class(global_status.status))
]
]
fields += host_detailed_status_list(host)
fields += [
[_("Domain"), (link_to(host.domain, hosts_path(:search => "domain = #{host.domain}")) if host.domain)],
[_("Realm"), (link_to(host.realm, hosts_path(:search => "realm = #{host.realm}")) if host.realm)],
[_("IP Address"), host.ip],
Expand All @@ -270,6 +280,17 @@ def overview_fields(host)
fields
end

def host_detailed_status_list(host)
host.host_statuses.sort_by(&:type).map do |status|
next unless status.relevant?
[
_(status.name),
content_tag(:i, ' '.html_safe, :class => host_global_status_icon_class(status.to_global)) +
content_tag(:span, _(status.to_label), :class => host_global_status_class(status.to_global))
]
end
end

def possible_images(cr, arch = nil, os = nil)
return cr.images unless controller_name == "hosts"
return [] unless arch && os
Expand Down
10 changes: 10 additions & 0 deletions app/models/concerns/configuration_status_scoped_search.rb
@@ -0,0 +1,10 @@
module ConfigurationStatusScopedSearch
extend ActiveSupport::Concern

module ClassMethods
def scoped_search_status(status, options)
options.merge({ :offset => Report::METRIC.index(status.to_s), :word_size => Report::BIT_NUM })
scoped_search options
end
end
end
18 changes: 11 additions & 7 deletions app/models/concerns/hostext/search.rb
Expand Up @@ -4,6 +4,7 @@ module Search

included do
include ScopedSearchExtensions
include ConfigurationStatusScopedSearch

has_many :search_parameters, :class_name => 'Parameter', :foreign_key => :reference_id
belongs_to :search_users, :class_name => 'User', :foreign_key => :owner_id
Expand All @@ -15,13 +16,16 @@ module Search
scoped_search :on => :managed, :complete_value => {:true => true, :false => false}
scoped_search :on => :owner_type, :complete_value => true, :only_explicit => true
scoped_search :on => :owner_id, :complete_enabled => false, :only_explicit => true
scoped_search :on => :puppet_status, :offset => 0, :word_size => Report::BIT_NUM*4, :complete_value => {:true => true, :false => false}, :rename => :'status.interesting'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("applied"), :word_size => Report::BIT_NUM, :rename => :'status.applied'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("restarted"), :word_size => Report::BIT_NUM, :rename => :'status.restarted'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("failed"), :word_size => Report::BIT_NUM, :rename => :'status.failed'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("failed_restarts"), :word_size => Report::BIT_NUM, :rename => :'status.failed_restarts'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("skipped"), :word_size => Report::BIT_NUM, :rename => :'status.skipped'
scoped_search :on => :puppet_status, :offset => Report::METRIC.index("pending"), :word_size => Report::BIT_NUM, :rename => :'status.pending'

scoped_search :in => :configuration_status_object, :on => :status, :offset => 0, :word_size => Report::BIT_NUM*4, :rename => :'status.interesting', :complete_value => {:true => true, :false => false}
scoped_search_status "applied", :in => :configuration_status_object, :on => :status, :rename => :'status.applied'
scoped_search_status "restarted", :in => :configuration_status_object, :on => :status, :rename => :'status.restarted'
scoped_search_status "failed", :in => :configuration_status_object, :on => :status, :rename => :'status.failed'
scoped_search_status "failed_restarts", :in => :configuration_status_object, :on => :status, :rename => :'status.failed_restarts'
scoped_search_status "skipped", :in => :configuration_status_object, :on => :status, :rename => :'status.skipped'
scoped_search_status "pending", :in => :configuration_status_object, :on => :status, :rename => :'status.pending'

scoped_search :on => :global_status, :complete_value => { :ok => HostStatus::Global::OK, :warning => HostStatus::Global::WARN, :error => HostStatus::Global::ERROR }

scoped_search :in => :model, :on => :name, :complete_value => true, :rename => :model
scoped_search :in => :hostgroup, :on => :name, :complete_value => true, :rename => :hostgroup
Expand Down
46 changes: 0 additions & 46 deletions app/models/concerns/report_common.rb

This file was deleted.

2 changes: 1 addition & 1 deletion app/models/host/hostmix.rb
Expand Up @@ -8,4 +8,4 @@ def belongs_to_host(options = {})
belongs_to :host, {:class_name => "Host::Managed", :foreign_key => :host_id}.merge(options)
end
end
end
end

0 comments on commit df74227

Please sign in to comment.