Project

General

Profile

« Previous | Next » 

Revision 2fba6ad7

Added by Ondřej Pražák over 8 years ago

Fixes #2267 - general rebuild of TFTP, DNS, DHCP

View differences:

app/controllers/api/v2/hosts_controller.rb
render_message(e.to_s, :status => :unprocessable_entity)
end
api :PUT, "/hosts/:id/rebuild_config", N_("Rebuild orchestration config")
param :id, :identifier_dottable, :required => true
def rebuild_config
result = @host.recreate_config
failures = result.reject { |key, value| value }.keys.map{ |k| _(k) }
if failures.empty?
render_message _("Configuration successfully rebuilt."), :status => :ok
else
render_message (_("Configuration rebuild failed for: %s.") % failures.to_sentence), :status => :unprocessable_entity
end
end
private
def merge_interfaces(host)
......
:edit
when 'vm_compute_attributes', 'get_status'
:view
when 'rebuild_config'
:build
else
super
end
app/controllers/hosts_controller.rb
multiple_destroy submit_multiple_destroy multiple_build
submit_multiple_build multiple_disable submit_multiple_disable
multiple_enable submit_multiple_enable multiple_puppetrun
update_multiple_puppetrun multiple_disassociate update_multiple_disassociate)
update_multiple_puppetrun multiple_disassociate update_multiple_disassociate
rebuild_config submit_rebuild_config)
add_smart_proxy_filters PUPPETMASTER_ACTIONS, :features => ['Puppet']
......
def multiple_build
end
def rebuild_config
end
def submit_rebuild_config
all_fails = {}
@hosts.each do |host|
result = host.recreate_config
result.each_pair do |k, v|
all_fails[k] ||= []
all_fails[k] << host.name unless v
end
end
message = ''
all_fails.each_pair do |key, values|
unless values.empty?
message << ((n_("%{config_type} rebuild failed for host: %{host_names}.",
"%{config_type} rebuild failed for hosts: %{host_names}.",
values.count) % {:config_type => _(key), :host_names => values.to_sentence})) + " "
end
end
if message.blank?
notice _('Configuration successfully rebuilt.')
else
error message
end
redirect_to hosts_path
end
def submit_multiple_build
@hosts.delete_if do |host|
forward_url_options(host)
......
:view
when 'puppetrun', 'multiple_puppetrun', 'update_multiple_puppetrun'
:puppetrun
when 'setBuild', 'cancelBuild', 'multiple_build', 'submit_multiple_build', 'review_before_build'
when 'setBuild', 'cancelBuild', 'multiple_build', 'submit_multiple_build', 'review_before_build',
'rebuild_config', 'submit_rebuild_config'
:build
when 'power'
:power
app/helpers/hosts_helper.rb
[_('Disable Notifications'), multiple_disable_hosts_path],
[_('Enable Notifications'), multiple_enable_hosts_path],
[_('Disassociate Hosts'), multiple_disassociate_hosts_path],
[_('Rebuild Config'), rebuild_config_hosts_path]
]
actions.insert(1, [_('Build Hosts'), multiple_build_hosts_path]) if SETTINGS[:unattended]
actions << [_('Run Puppet'), multiple_puppetrun_hosts_path] if Setting[:puppetrun]
app/helpers/provisioning_templates_helper.rb
template.respond_to?(:operatingsystem_ids) &&
template.template_kind.present? &&
kinds.include?(template.template_kind.name) &&
Host.where(:build => true, :operatingsystem_id => template.operatingsystem_ids).any?
building_hosts(template).any?
end
def building_hosts(template)
Host.where(:build => true, :operatingsystem_id => template.operatingsystem_ids)
end
def building_hosts_path(template)
oses = template.operatingsystem_ids.map { |id| "os_id = #{id}" }.join(" or ")
hosts_path(:search => "build = true and ( #{oses} )")
end
private
app/models/concerns/orchestration.rb
after_destroy :on_destroy
end
module ClassMethods
def rebuild_methods
@rebuild_methods || {}
end
def rebuild_methods=(methods)
@rebuild_methods = methods || {}
end
def register_rebuild(method, pretty_name)
@rebuild_methods ||= {}
fail "Method :#{method} is already registered, choose different name for your method" if @rebuild_methods[method]
@rebuild_methods.merge!(method => pretty_name)
end
end
protected
def on_save
app/models/concerns/orchestration/dhcp.rb
after_validation :dhcp_conflict_detected?, :queue_dhcp
before_destroy :queue_dhcp_destroy
validate :ip_belongs_to_subnet?
register_rebuild(:rebuild_dhcp, N_('DHCP'))
end
def dhcp?
......
@dhcp_record ||= (provision? && jumpstart?) ? Net::DHCP::SparcRecord.new(dhcp_attrs) : Net::DHCP::Record.new(dhcp_attrs)
end
def rebuild_dhcp
if dhcp?
del_dhcp_safe
begin
set_dhcp
rescue => e
Foreman::Logging.exception "Failed to rebuild DHCP record for #{name}, #{ip}", e, :level => :error
false
end
else
logger.info "DHCP not supported for #{name}, #{ip}, skipping orchestration rebuild"
true
end
end
protected
def del_dhcp_safe
if dhcp_record
begin
del_dhcp
rescue => e
Foreman::Logging.exception "Proxy failed to delete DHCP record for #{name}, #{ip}", e, :level => :error
end
end
end
def set_dhcp
dhcp_record.create
end
app/models/concerns/orchestration/dns.rb
included do
after_validation :dns_conflict_detected?, :queue_dns
before_destroy :queue_dns_destroy
register_rebuild(:rebuild_dns, N_('DNS'))
end
def dns?
......
(host.nil? || host.managed?) && managed? && hostname.present? && ip_available? && !subnet.nil? && subnet.dns? && SETTINGS[:unattended]
end
def rebuild_dns
logger.info "DNS not supported for #{name}, #{ip}, skipping orchestration rebuild" unless dns?
logger.info "Reverse DNS not supported for #{name}, #{ip}, skipping orchestration rebuild" unless reverse_dns?
return true unless dns? || reverse_dns?
del_dns_a_record_safe
del_dns_ptr_record_safe
a_record_result, ptr_record_result = true, true
begin
a_record_result = recreate_a_record if dns?
ptr_record_result = recreate_ptr_record if reverse_dns?
a_record_result && ptr_record_result
rescue => e
Foreman::Logging.exception "Failed to rebuild DNS record for #{name}, #{ip}", e, :level => :error
false
end
end
def dns_a_record
return unless dns? or @dns_a_record
@dns_a_record ||= Net::DNS::ARecord.new dns_record_attrs
......
@dns_ptr_record ||= Net::DNS::PTRRecord.new reverse_dns_record_attrs
end
def del_dns_a_record_safe
if dns_a_record
begin
del_dns_a_record
rescue => e
Foreman::Logging.exception "Proxy failed to delete DNS a_record for #{name}, #{ip}", e, :level => :error
end
end
end
def del_dns_ptr_record_safe
if dns_ptr_record
begin
del_dns_ptr_record
rescue => e
Foreman::Logging.exception "Proxy failed to delete DNS ptr_record for #{name}, #{ip}", e, :level => :error
end
end
end
protected
def recreate_a_record
set_dns_a_record unless dns_a_record.nil? || dns_a_record.valid?
end
def recreate_ptr_record
set_dns_ptr_record unless dns_ptr_record.nil? || dns_ptr_record.valid?
end
def set_dns_a_record
dns_a_record.create
end
app/models/concerns/orchestration/tftp.rb
# required for pxe template url helpers
include Rails.application.routes.url_helpers
register_rebuild(:rebuild_tftp, N_('TFTP'))
end
def tftp?
......
subnet.tftp_proxy(:variant => host.operatingsystem.pxe_variant) if tftp?
end
def rebuild_tftp
if tftp?
begin
setTFTP
rescue => e
Foreman::Logging.exception "Failed to rebuild TFTP record for #{name}, #{ip}", e, :level => :error
false
end
else
logger.info "TFTP not supported for #{name}, #{ip}, skipping orchestration rebuild"
true
end
end
protected
# Adds the host to the forward and reverse TFTP zones
app/models/host/managed.rb
def build_status_label(options = {})
@build_status_label ||= get_status(HostStatus::BuildStatus).to_label(options)
end
# rebuilds orchestration configuration for a host
# takes all the methods from Orchestration modules that are registered for configuration rebuild
# returns : Hash with 'true' if rebuild was a success for a given key (Example: {"TFTP" => true, "DNS" => false})
def recreate_config
result = {}
Nic::Managed.rebuild_methods.map do |method, pretty_name|
interfaces.map do |interface|
value = interface.send method
result[pretty_name] = value if !result.has_key?(pretty_name) || (result[pretty_name] && !value)
end
end
self.class.rebuild_methods.map do |method, pretty_name|
raise ::Foreman::Exception.new(N_("There are orchestration modules with methods for configuration rebuild that have identical name: '%s'") % pretty_name) if result[pretty_name]
result[pretty_name] = self.send method
end
result
end
private
app/services/foreman/access_permissions.rb
:"api/v2/hosts" => [:destroy],
:"api/v2/interfaces" => [:destroy]
}
map.permission :build_hosts, {:hosts => [:setBuild, :cancelBuild, :multiple_build, :submit_multiple_build, :review_before_build],
map.permission :build_hosts, {:hosts => [:setBuild, :cancelBuild, :multiple_build, :submit_multiple_build, :review_before_build,
:rebuild_config, :submit_rebuild_config],
:tasks => tasks_ajax_actions,
:"api/v2/tasks" => [:index] }
:"api/v2/tasks" => [:index],
:"api/v2/hosts" => [:rebuild_config],
}
map.permission :power_hosts, {:hosts => [:power],
:"api/v2/hosts" => [:power] }
map.permission :console_hosts, {:hosts => [:console] }
app/views/hosts/rebuild_config.html.erb
<%= render 'selected_hosts', :hosts => @hosts %>
<%= form_tag submit_rebuild_config_hosts_path(:host_ids => params[:host_ids]), :onsubmit => "resetSelection()" do %>
<small><%= _('Rebuild orchestration configuration') %></small>
<% end %>
app/views/templates/_form.html.erb
<% if pxe_with_building_hosts?(@template) -%>
<% warning_text = (_("Warning: The template is associated to at least one host in build mode. To apply the change, disable and enable build mode on hosts to update the live templates or choose to %s their configuration from 'Select Action' menu") % link_to(_('recreate'), building_hosts_path(@template))).html_safe %>
<%= alert :class => 'alert-warning', :header => '',
:text => icon_text("warning-sign", (_("Warning: The template is associated to at least one host in build mode. To apply the change, disable and enable build mode on hosts to update the live templates."))) %>
:text => icon_text("warning-sign", warning_text) %>
<% end -%>
<div class='form-group'>
config/routes.rb
post 'update_multiple_organization'
get 'select_multiple_location'
post 'update_multiple_location'
get 'rebuild_config'
post 'submit_rebuild_config'
end
constraints(:host_id => /[^\/]+/) do
config/routes/api/v2.rb
put :disassociate, :on => :member
put :boot, :on => :member
put :power, :on => :member
put :rebuild_config, :on => :member
post :facts, :on => :collection
resources :audits, :only => :index
resources :facts, :only => :index, :controller => :fact_values
test/functional/api/v2/hosts_controller_test.rb
assert_response :success
end
def test_rebuild_config_optimistic
Host.any_instance.expects(:recreate_config).returns({ "TFTP" => true, "DNS" => true, "DHCP" => true })
host = FactoryGirl.create(:host)
post :rebuild_config, { :id => host.to_param }, set_session_user
assert_response :success
end
def test_rebuild_config_pessimistic
Host.any_instance.expects(:recreate_config).returns({ "TFTP" => false, "DNS" => false, "DHCP" => false })
host = FactoryGirl.create(:host)
post :rebuild_config, { :id => host.to_param }, set_session_user
assert_response 422
end
def test_create_valid_node_from_json_facts_object_without_certname
User.current=nil
hostname = fact_json['name']
test/functional/hosts_controller_test.rb
assert_template :partial => '_form'
end
def test_submit_multiple_rebuild_config_optimistic
@request.env['HTTP_REFERER'] = hosts_path
Host.any_instance.expects(:recreate_config).returns({"TFTP" => true, "DHCP" => true, "DNS" => true})
h = FactoryGirl.create(:host)
post :submit_rebuild_config, {:host_ids => [h.id]}, set_session_user
assert_response :found
assert_redirected_to hosts_path
assert_not_nil flash[:notice]
end
def test_submit_multiple_rebuild_config_pessimistic
@request.env['HTTP_REFERER'] = hosts_path
Host.any_instance.expects(:recreate_config).returns({"TFTP" => false, "DHCP" => false, "DNS" => false})
h = FactoryGirl.create(:host)
post :submit_rebuild_config, {:host_ids => [h.id]}, set_session_user
assert_response :found
assert_redirected_to hosts_path
assert_not_nil flash[:error]
end
private
def initialize_host
test/unit/host_test.rb
end
end
context "recreating host config" do
setup do
@nic = FactoryGirl.build(:nic_primary_and_provision)
@nic.expects(:rebuild_tftp).returns(true)
@nic.expects(:rebuild_dns).returns(true)
@nic.expects(:rebuild_dhcp).returns(true)
Nic::Managed.expects(:rebuild_methods).returns(:rebuild_dhcp => "DHCP", :rebuild_dns => "DNS", :rebuild_tftp => "TFTP")
end
test "recreate config with success" do
Host::Managed.expects(:rebuild_methods).returns(:rebuild_test => "TEST")
host = FactoryGirl.build(:host, :interfaces => [@nic])
host.expects(:rebuild_test).returns(true)
result = host.recreate_config
assert result["DHCP"]
assert result["DNS"]
assert result["TFTP"]
assert result["TEST"]
end
test "recreate config with clashing methods" do
Host::Managed.expects(:rebuild_methods).returns(:rebuild_dns => "DNS")
host = FactoryGirl.build(:host, :interfaces => [@nic])
assert_raises(Foreman::Exception) { host.recreate_config }
end
context "recreate with multiple nics and failures" do
setup do
@nic2 = FactoryGirl.build(:nic_managed)
@nic2.expects(:rebuild_tftp).returns(false)
@nic2.expects(:rebuild_dns).returns(false)
@nic2.expects(:rebuild_dhcp).returns(false)
end
test "second is a failure" do
host = FactoryGirl.build(:host, :interfaces => [@nic, @nic2])
result = host.recreate_config
refute result["DHCP"]
refute result["DNS"]
refute result["TFTP"]
end
test "first is a failure" do
host = FactoryGirl.build(:host, :interfaces => [@nic2, @nic])
result = host.recreate_config
refute result["DHCP"]
refute result["DNS"]
refute result["TFTP"]
end
end
test "recreate with multiple nics, all are success" do
nic = FactoryGirl.build(:nic_managed)
nic.expects(:rebuild_tftp).returns(true)
nic.expects(:rebuild_dns).returns(true)
nic.expects(:rebuild_dhcp).returns(true)
host = FactoryGirl.build(:host, :interfaces => [@nic, nic])
result = host.recreate_config
assert result["DHCP"]
assert result["DNS"]
assert result["TFTP"]
end
end
private
def parse_json_fixture(relative_path)
test/unit/orchestration/dhcp_test.rb
refute h.valid?
assert_equal h.errors[:operatingsystem_id].first, "can't be blank"
end
test "should rebuild dhcp" do
h = FactoryGirl.create(:host, :with_dhcp_orchestration)
Nic::Managed.any_instance.expects(:del_dhcp)
Nic::Managed.any_instance.expects(:set_dhcp).returns(true)
assert h.interfaces.first.rebuild_dhcp
end
test "should skip dhcp rebuild" do
nic = FactoryGirl.build(:nic_managed)
nic.expects(:set_dhcp).never
assert nic.rebuild_dhcp
end
test "should fail with exception" do
h = FactoryGirl.create(:host, :with_dhcp_orchestration)
Nic::Managed.any_instance.expects(:del_dhcp)
Nic::Managed.any_instance.expects(:set_dhcp).raises(StandardError, 'DHCP test failure')
refute h.interfaces.first.rebuild_dhcp
end
end
test/unit/orchestration/dns_test.rb
assert_equal false, h.reverse_dns?
end
end
def test_should_rebuild_dns
h = FactoryGirl.create(:host, :with_dns_orchestration)
Nic::Managed.any_instance.expects(:del_dns_a_record)
Nic::Managed.any_instance.expects(:del_dns_ptr_record)
Nic::Managed.any_instance.expects(:recreate_a_record).returns(true)
Nic::Managed.any_instance.expects(:recreate_ptr_record).returns(true)
assert h.interfaces.first.rebuild_dns
end
def test_should_skip_dns_rebuild
nic = FactoryGirl.build(:nic_managed)
Nic::Managed.any_instance.expects(:del_dns_a_record).never
Nic::Managed.any_instance.expects(:del_dns_ptr_record).never
Nic::Managed.any_instance.expects(:recreate_a_record).never
Nic::Managed.any_instance.expects(:recreate_ptr_record).never
assert nic.rebuild_dns
end
def test_dns_rebuild_should_fail
h = FactoryGirl.create(:host, :with_dns_orchestration)
Nic::Managed.any_instance.expects(:del_dns_a_record)
Nic::Managed.any_instance.expects(:del_dns_ptr_record)
Nic::Managed.any_instance.expects(:recreate_a_record).returns(true)
Nic::Managed.any_instance.expects(:recreate_ptr_record).returns(false)
refute h.interfaces.first.rebuild_dns
end
def test_dns_rebuild_should_fail_with_exception
h = FactoryGirl.create(:host, :with_dns_orchestration)
Nic::Managed.any_instance.expects(:del_dns_a_record)
Nic::Managed.any_instance.expects(:del_dns_ptr_record)
Nic::Managed.any_instance.expects(:recreate_a_record).returns(true)
Nic::Managed.any_instance.stubs(:recreate_ptr_record).raises(StandardError, 'DNS test fail')
refute h.interfaces.first.rebuild_dns
end
end
test/unit/orchestration/tftp_test.rb
assert_equal template,expected
end
end
def test_should_rebuild_tftp
h = FactoryGirl.create(:host, :with_tftp_orchestration)
Nic::Managed.any_instance.expects(:setTFTP).returns(true)
assert h.interfaces.first.rebuild_tftp
end
def test_should_fail_rebuild_tftp_with_exception
h = FactoryGirl.create(:host, :with_tftp_orchestration)
Nic::Managed.any_instance.expects(:setTFTP).raises(StandardError, 'TFTP rebuild failed')
refute h.interfaces.first.rebuild_tftp
end
def test_should_skip_rebuild_tftp
nic = FactoryGirl.build(:nic_managed)
nic.expects(:setTFTP).never
assert nic.rebuild_tftp
end
end
test/unit/orchestration_test.rb
@nic.valid?
SETTINGS[:unattended] = original
end
context "when subscribing orchestration methods to nic" do
setup do
module Orchestration::TestModule
extend ActiveSupport::Concern
included do
register_rebuild(:rebuild_test, N_('TEST'))
end
def rebuild_test
end
end
@nic.class.send :include, Orchestration::TestModule
end
test "register_rebuild can register methods" do
assert @nic.class.respond_to? :register_rebuild
assert @nic.class.ancestors.include? Orchestration::TestModule
end
test "we can retrieve registered methods" do
assert @nic.class.rebuild_methods.keys.include? :rebuild_test
end
end
context "when subscribing orchestration methods to host" do
setup do
module Orchestration::HostTest
extend ActiveSupport::Concern
included do
register_rebuild(:rebuild_host, N_('HOST'))
end
def rebuild_host
end
end
@host.class.send :include, Orchestration::HostTest
end
test "register_rebuild can register methods" do
assert @host.class.respond_to? :register_rebuild
assert @host.class.ancestors.include? Orchestration::HostTest
end
test "we can retrieve registered methods" do
assert @host.class.rebuild_methods.keys.include? :rebuild_host
end
test "we cannot override already subscribed methods" do
module Orchestration::HostTest2
extend ActiveSupport::Concern
included do
register_rebuild(:rebuild_host, N_('HOST'))
end
def rebuild_host
end
end
assert_raises(RuntimeError) { @host.class.send :include, Orchestration::HostTest2 }
end
end
end

Also available in: Unified diff