Skip to content

Commit

Permalink
Fixes #3860 - automated discovery provisioning
Browse files Browse the repository at this point in the history
  • Loading branch information
lzap authored and GregSutcliffe committed Dec 15, 2014
1 parent b221e9c commit d3e7c8b
Show file tree
Hide file tree
Showing 34 changed files with 906 additions and 20 deletions.
64 changes: 63 additions & 1 deletion app/controllers/api/v2/discovered_hosts_controller.rb
@@ -1,8 +1,9 @@
module Api
module V2
class DiscoveredHostsController < ::Api::V2::BaseController
include Foreman::Controller::DiscoveredExtensions

before_filter :find_resource, :except => %w{index create facts}
before_filter :find_resource, :except => %w{index create facts auto_provision_all}
skip_before_filter :authorize, :only => :facts

resource_description do
Expand Down Expand Up @@ -95,11 +96,61 @@ def destroy

def facts
@discovered_host, state = Host::Discovered.import_host_and_facts(params[:facts])
if state && rule = find_discovery_rule(@discovered_host)
state = perform_auto_provision(@discovered_host.becomes(::Host::Managed), rule) if Setting['discovery_auto']
else
Rails.logger.warn "Discovered facts import unsuccessful, skipping auto provisioning"
end
process_response state
rescue ::Foreman::Exception => e
render :json => {'message'=>e.to_s}, :status => :unprocessable_entity
end

api :POST, "/discovered_hosts/:id/auto_provision", N_("Execute rules against a discovered host")

def auto_provision
@discovered_host.transaction do
if rule = find_discovery_rule(@discovered_host)
msg = _("Host %s was provisioned with rule %s") % [@discovered_host.name, rule.name]
process_response perform_auto_provision(@discovered_host.becomes(::Host::Managed), rule), msg
else
process_success _("No rule found for host %s") % @discovered_host.name
end
end
rescue ::Foreman::Exception => e
render :json => {'message' => e.to_s}, :status => :unprocessable_entity
end

api :POST, "/discovered_hosts/auto_provision_all", N_("Execute rules against all currently discovered hosts")

def auto_provision_all
result = true
Host.transaction do
overall_errors = ""
Host::Discovered.all.each do |discovered_host|
if rule = find_discovery_rule(discovered_host)
result &= perform_auto_provision(discovered_host.becomes(::Host::Managed), rule)
unless discovered_host.errors.empty?
errors = discovered_host.errors.full_messages.join(' ')
logger.warn "Failed to auto provision host %s: %s" % [discovered_host.name, errors]
overall_errors << "#{discovered_host.name}: #{errors} "
end
else
logger.warn "No rule found for host %s" % discovered_host.name
end
end
if result
process_success _("Discovered hosts are provisioning now")
else
render_error :custom_error,
:status => :unprocessable_entity,
:locals => {
:message => _("Errors during auto provisioning: %s") % overall_errors
}
end
end
end

private

def resource_class
Expand All @@ -110,6 +161,17 @@ def forward_request_url
@discovered_host.request_url = request.host_with_port if @discovered_host.respond_to?(:request_url)
end

def action_permission
case params[:action]
when 'auto_provision'
:auto_provision
when 'auto_provision_all'
:auto_provision_all
else
super
end
end

end
end
end
79 changes: 79 additions & 0 deletions app/controllers/api/v2/discovery_rules_controller.rb
@@ -0,0 +1,79 @@
module Api
module V2
class DiscoveryRulesController < ::Api::V2::BaseController

before_filter :find_resource, :except => %w{index create facts}

resource_description do
resource_id 'discovery_rules'
api_version 'v2'
api_base_url "/api/v2"
end

api :GET, "/discovery_rules/", N_("List all discovery rules")
param :search, String, :desc => N_("filter results")
param :order, String, :desc => N_("sort results")
param :page, String, :desc => N_("paginate results")
param :per_page, String, :desc => N_("number of entries per request")

def index
@discovery_rules = resource_scope.search_for(*search_options).paginate(paginate_options)
end

api :GET, "/discovery_rules/:id/", N_("Show a discovery rule")
param :id, :identifier_dottable, :required => true

def show
end

def_param_group :discovery_rule do
param :discovery_rule, Hash, :action_aware => true do
param :name, String, :required => true
param :search, String, :required => true
param :hostgroup_id, Integer, :required => true
param :hostname, String, :required => true
param :max_count, Integer
param :priority, Integer
param :enabled, :bool
end
end

api :POST, "/discovery_rules/", N_("Create a discovery rule")
param_group :discovery_rule, :as => :create

def create
@discovery_rule = DiscoveryRule.new(params[:discovery_rule])
process_response @discovery_rule.save
end

api :PUT, "/discovery_rules/:id/", N_("Update a rule")
param :id, :identifier, :required => true
param :discovery_rule, Hash, :action_aware => true do
param :name, String, :required => true
param :search, String, :required => true
param :hostgroup_id, Integer, :required => true
param :hostname, String, :required => true
param :max_count, Integer
param :priority, Integer
param :enabled, :bool
end

def update
process_response @discovery_rule.update_attributes(params[:discovery_rule])
end

api :DELETE, "/discovery_rules/:id/", N_("Delete a rule")
param :id, :identifier, :required => true

def destroy
process_response @discovery_rule.destroy
end

private

def resource_class
DiscoveryRule
end
end
end
end
@@ -0,0 +1,42 @@
module Foreman::Controller::DiscoveredExtensions
extend ActiveSupport::Concern

# return auto provision rule or false when not present
def find_discovery_rule host
Rails.logger.debug "Finding auto discovery rule for host #{host.name} (#{host.id})"
# for each discovery rule ordered by priority
DiscoveryRule.where(:enabled => true).order(:priority).each do |rule|
max = rule.max_count
usage = rule.hosts.size
Rails.logger.debug "Found rule #{rule.name} (#{rule.id}) [#{usage}/#{max}]"
# if the rule has free slots
if max == 0 || usage < max
# try to match the search
begin
if Host::Discovered.where(:id => host.id).search_for(rule.search).size > 0
Rails.logger.info "Match found for host #{host.name} (#{host.id}) rule #{rule.name} (#{rule.id})"
return rule
end
rescue ScopedSearch::QueryNotSupported => e
Rails.logger.warn "Invalid query for rule #{rule.name} (#{rule.id}): #{e.message}"
end
else
Rails.logger.info "Skipping drained rule #{rule.name} (#{rule.id}) with max set to #{rule.max_count}"
end
end
return false
end

# trigger the provisioning
def perform_auto_provision host, rule
host.type = 'Host::Managed'
host.managed = true
host.build = true
host.name = host.render_template(rule.hostname) unless rule.hostname.empty?
host.hostgroup_id = rule.hostgroup_id
host.comment = "Auto-discovered and provisioned via rule '#{rule.name}'"
host.discovery_rule = rule
host.save # save! does not work here
end

end
45 changes: 44 additions & 1 deletion app/controllers/discovered_hosts_controller.rb
@@ -1,12 +1,13 @@
class DiscoveredHostsController < ::ApplicationController
include Foreman::Controller::AutoCompleteSearch
include Foreman::Controller::TaxonomyMultiple
include Foreman::Controller::DiscoveredExtensions
unloadable

# Avoid auth for discovered host creation
skip_before_filter :require_login, :require_ssl, :authorize, :verify_authenticity_token, :set_taxonomy, :session_expiry, :update_activity_time, :only => :create

before_filter :find_by_name, :only => %w[show edit update destroy refresh_facts convert]
before_filter :find_by_name, :only => %w[show edit update destroy refresh_facts convert auto_provision]
before_filter :find_multiple, :only => [:multiple_destroy, :submit_multiple_destroy]
before_filter :taxonomy_scope, :only => [:edit]

Expand Down Expand Up @@ -121,6 +122,44 @@ def auto_complete_search
render :json => @items
end

def auto_provision
@host.transaction do
if rule = find_discovery_rule(@host)
if perform_auto_provision(@host.becomes(::Host::Managed), rule)
process_success :success_msg => _("Host %s was provisioned with rule %s") % [@host.name, rule.name], :success_redirect => :back
else
errors = @host.errors.full_messages.join(' ')
logger.warn "Failed to auto provision host %s: %s" % [@host.name, errors]
process_error :error_msg => _("Failed to auto provision host %s: %s") % [@host.name, errors], :redirect => :back
end
else
process_success :success_msg => _("No rule found for host %s") % @host.name, :success_redirect => :back
end
end
end

def auto_provision_all
result = true
Host.transaction do
overall_errors = ""
Host::Discovered.all.each do |discovered_host|
if rule = find_discovery_rule(discovered_host)
result &= perform_auto_provision(discovered_host.becomes(::Host::Managed), rule)
unless discovered_host.errors.empty?
errors = discovered_host.errors.full_messages.join(' ')
logger.warn "Failed to auto provision host %s: %s" % [discovered_host.name, errors]
overall_errors << "#{discovered_host.name}: #{errors} "
end
end
end
if result
process_success :success_msg => _("Discovered hosts are provisioning now"), :success_redirect => :back
else
process_error :error_msg => _("Errors during auto provisioning: %s") % overall_errors, :redirect => :back
end
end
end

private

def resource_base
Expand Down Expand Up @@ -162,6 +201,10 @@ def action_permission
:edit
when 'submit_multiple_destroy', 'multiple_destroy'
:destroy
when 'auto_provision'
:auto_provision
when 'auto_provision_all'
:auto_provision_all
else
super
end
Expand Down
73 changes: 73 additions & 0 deletions app/controllers/discovery_rules_controller.rb
@@ -0,0 +1,73 @@
class DiscoveryRulesController < ApplicationController
# Two-pane searching must be disabled for rules otherwise query completion will not work
#include Foreman::Controller::AutoCompleteSearch

before_filter :find_resource, :only => [:edit, :update, :destroy, :enable, :disable, :auto_provision]

def index
base = resource_base.search_for(params[:search], :order => (params[:order] || 'priority ASC'))
@discovery_rules = base.paginate(:page => params[:page]).includes(:hostgroup)
end

def new
@discovery_rule = DiscoveryRule.new
end

def create
@discovery_rule = DiscoveryRule.new(params[:discovery_rule])
if @discovery_rule.save
process_success
else
process_error
end
end

def edit
end

def update
if @discovery_rule.update_attributes(params[:discovery_rule])
process_success
else
process_error
end
end

def destroy
if @discovery_rule.destroy
process_success
else
process_error
end
end

def enable
set_enabled true
end

def disable
set_enabled false
end

private

def action_permission
case params[:action]
when 'enable'
:enable
when 'disable'
:disable
else
super
end
end

def set_enabled(enabled)
@discovery_rule.enabled = enabled
if @discovery_rule.save
process_success :success_msg => (enabled ? _('Rule enabled') : _('Rule disabled')), :success_redirect => :back
else
process_error
end
end
end
3 changes: 3 additions & 0 deletions app/helpers/discovered_hosts_helper.rb
Expand Up @@ -9,6 +9,9 @@ def discovered_hosts_title_actions(host)
button_group(
link_to(_("Provision"), hash_for_edit_discovered_host_path(:id => host), :class => 'btn-primary')
),
button_group(
link_to(_("Auto Provision"), hash_for_auto_provision_discovered_host_path(:id => host), :method => :post)
),
button_group(
link_to(_("Refresh facts"), hash_for_refresh_facts_discovered_host_path(:id => host))
),
Expand Down

0 comments on commit d3e7c8b

Please sign in to comment.