Project

General

Profile

« Previous | Next » 

Revision 43c4bd72

Added by Marek Hulán over 9 years ago

Fixes #7456 - Extract primary interface from host

Contributions from:

All host must have at least one primary interface and one provision (can
be the same interface). Primary interface gives host a name so even
unamanaged host have primary interface (we skip validations of other
attributes for unamanged hosts though).

Host still have name attribute which is a cache of primary interface name.
Therefore we can use the host name in SQL queries, as a friendly_id etc.

- realm moved to the primary tab
- fqdn in nics table
- flags in nics table
- checkboxes for provision and primary flags
- modal resize fix
- original fields for primary NIC removed
- skipping validation for new resources
- warnings before switching flags and table update
- host name and primary interface name connected
- host domain name in the page title
- nics on host show page
- clearing modal window on cancel
- fixed domain validation in NIC::Base
- ip suggestion for all interfaces
- flags switchable from the overview table
- use icons instead of text for primary/provision on NICs overview tab
- attempt to fix sending NIC template
- fix fqdn algorithm
- ip addres in the overview table
- fix for class name collision
- better behavior of host name <-> primary name sync
- fix for subnet combobox values
- fix for modal poping up on form submission
- network partial for CRs moved from VM tab to modal
- fix ip suggestion race for ipam=db

View differences:

app/assets/javascripts/host_edit.js
$(document).on('AddedClass', function(event, link){load_puppet_class_parameters(link)});
$(document).on('click', '#params-tab', function() { resizeTextareas($('#params')); });
function update_nics(success_callback) {
var data = $('form').serialize().replace('method=put', 'method=post');
$('#network').html(spinner_placeholder(__('Loading interfaces information ...')));
$('#network_tab a').removeClass('tab-error');
var url = '/hosts/interfaces';
$.ajax({
type:'post',
url: url,
data: data,
complete: function(){},
error: function(jqXHR, status, error){
$('#network').html(Jed.sprintf(__("Error loading interfaces information: %s"), error));
$('#network_tab a').addClass('tab-error');
},
success: function(result){
$('#network').html(result);
if ($('#network').find('.alert-danger').length > 0)
$('#network_tab a').addClass('tab-error');
success_callback();
}
})
}
function computeResourceSelected(item){
var compute = $(item).val();
if (compute == '' && /compute_resource/.test($(item).attr('name'))) {
//Bare metal compute resource
$('#mac_address').show();
$("#model_name").show();
$('#compute_resource').empty();
$('#vm_details').empty();
......
update_capabilities('build');
} else {
//Real compute resource or any compute profile
$('#mac_address').hide();
$("#model_name").hide();
$("#compute_resource_tab").show();
$("#compute_profile").show();
......
}
})
}
update_nics(function() {
interface_subnet_selected(primary_nic_form().find('.interface_subnet'));
});
}
function update_capabilities(capabilities){
......
success: function(response) {
$('form').replaceWith(response);
multiSelectOnLoad();
$("[id$='subnet_id']").first().change();
// to handle case if def process_taxonomy changed compute_resource_id to nil
if( !$('#host_compute_resource_id').val() ) {
$('#host_compute_resource_id').change();
}
update_capabilities($('#host_compute_resource_id').val() ? $('#capabilities').val() : 'build');
$(document.body).trigger('ContentLoad');
}
})
}
function subnet_selected(element){
var ipam_text = $("#host_ip").parentsUntil('.clearfix').find(".help-block,.help-inline");
if (selectedSubnetHasIPAM()) {
ipam_text.removeClass('hide')
} else {
ipam_text.addClass('hide');
return false
}
var subnet_id = $(element).val();
if (subnet_id == '' || $('#host_ip').length == 0) return;
// We do not query the proxy if the host_ip field is filled in and contains an
// IP that is in the selected subnet
var drop_text = $(element).children(":selected").text();
if (drop_text.length !=0 && drop_text.search(/^.+ \([0-9\.\/]+\)/) != -1) {
var details = drop_text.replace(/^.+\(/, "").replace(")","").split("/");
if (subnet_contains(details[0], details[1], $('#host_ip').val()))
return;
}
var attrs = attribute_hash(["subnet_id", "host_mac", 'organization_id', 'location_id']);
$(element).indicator_show();
var url = $(element).data('url');
$.ajax({
data: attrs,
type:'post',
url: url,
complete: function(){ $(element).indicator_hide();},
success: function(data){
$('#host_ip').val(data.ip);
$(document.body).trigger('ContentLoad');
}
})
}
......
return integer;
}
function domain_selected(element){
var attrs = attribute_hash(['domain_id', 'organization_id', 'location_id']);
var url = $(element).data('url');
$(element).indicator_show();
$.ajax({
data: attrs,
type:'post',
url: url,
complete: function(){ $(element).indicator_hide();},
success: function(request) {
$('#subnet_select').html(request);
reload_host_params();
}
})
}
function architecture_selected(element){
var attrs = attribute_hash(['architecture_id', 'organization_id', 'location_id']);
var url = $(element).attr('data-url');
......
interface_domain_selected(this);
});
$(document).on('click', '#suggest_new_ip', function (e) {
$('#host_ip').val('')
interface_subnet_selected($('#host_subnet_id'));
$(document).on('click', '.suggest_new_ip', function (e) {
$(this).closest('fieldset').find('.interface_ip').val('');
interface_subnet_selected($(this).closest('fieldset').find('.interface_subnet'));
e.preventDefault();
});
......
subnet_options.append($("<option />").val(null).text(__('Please select')));
$.each(result, function () {
subnet_options.append($("<option />").val(this.subnet.id).text(this.subnet.name + ' (' + this.subnet.to_label + ')'));
subnet_options.append($("<option />").val(this.subnet.id).text(this.subnet.to_label));
});
if (subnet_options.find('option').length > 0) {
subnet_options.attr('disabled', false);
......
return;
}
}
var interface_mac = $(element).parentsUntil('.fields').parent().find('input[id$=_mac]')
var interface_mac = $(element).closest('fieldset').find('input[id$=_mac]');
var url = $(element).attr('data-url');
var org = $('#host_organization_id :selected').val();
var loc = $('#host_location_id :selected').val();
var data = {subnet_id: subnet_id, host_mac: interface_mac.val(), organization_id:org, location_id:loc }
var taken_ips = $(active_interface_forms()).find('.interface_ip').map(function() {
return $(this).val();
}).get();
taken_ips.push(interface_ip.val());
var data = {
subnet_id: subnet_id,
host_mac: interface_mac.val(),
organization_id: org,
location_id: loc,
taken_ips: taken_ips
}
$.ajax({
data: data,
type:'post',
......
dataType:'json',
success:function (result) {
interface_ip.val(result['ip']);
update_interface_table();
},
complete:function () {
$(element).indicator_hide();
......
function interface_type_selected(element) {
var fieldset = $(element).closest("fieldset");
var data = fieldset.serializeArray();
data.push({
name: 'host[compute_resource_id]',
value: $('#host_compute_resource_id').val()
})
$.ajax({
data: fieldset.serialize(),
data: data,
type: 'GET',
url: fieldset.attr('data-url'),
dataType: 'script'
app/assets/javascripts/host_edit_interfaces.js
modal_window.find('.modal-body').append(modal_content.contents());
modal_window.find('.modal-title').html(__('Interface') + ' ' + String(identifier));
modal_window.modal({'show': true});
modal_window.find('a[rel="popover-modal"]').popover({html: true});
}
function close_interface_modal() {
function save_interface_modal() {
var modal_window = $('#interfaceModal');
var interface_id = modal_window.data('current-id');
var interface_row = get_interface_row(interface_id);
update_interface_row(interface_row, modal_window);
modal_window.modal('hide');
modal_window.removeData('current-id');
var modal_form = modal_window.find('.modal-body').contents();
if (modal_form.find('.interface_primary').is(':checked')) {
$('#interfaceForms .interface_primary:checked').attr("checked", false);
}
if (modal_form.find('.interface_provision').is(':checked')) {
$('#interfaceForms .interface_provision:checked').attr("checked", false);
}
var interface_hidden = get_interface_hidden(interface_id);
interface_hidden.html('');
interface_hidden.append(modal_window.find('.modal-body').contents());
interface_hidden.append(modal_form);
close_interface_modal();
sync_primary_name(false);
update_interface_table();
update_fqdn();
}
function sync_primary_name(ovewrite_blank) {
var nic_name = primary_nic_form().find('.interface_name');
var host_name = $('#host_name');
if (ovewrite_blank && (nic_name.val().length == 0))
nic_name.val(host_name.val());
else
host_name.val(nic_name.val());
}
function close_interface_modal() {
var modal_window = $('#interfaceModal');
modal_window.modal('hide');
modal_window.removeData('current-id');
modal_window.find('.modal-body').html('');
}
function get_interface_template_clone() {
var content = $('.interfaces_fields_template').html();
var content = $('#interfaces .interfaces_fields_template').html();
var interface_id = new Date().getTime();
content = fix_template_names(content, 'interfaces', interface_id);
......
hidden.attr('id', 'interfaceHidden'+interface_id);
hidden.data('interface-id', interface_id);
hidden.find('.destroyFlag').val(0);
return hidden;
}
......
if ( interface_row.length == 0) {
interface_row = $('#interfaceTemplate').clone(true);
interface_row.attr('id', 'interface'+interface_id);
interface_row.data('interface-id', interface_id);
interface_row.find('.showModal').click( function(){
edit_interface(interface_id);
......
if ( interface_hidden.length == 0) {
interface_hidden = $('<div></div>');
interface_hidden.attr('style', 'display: none');
interface_hidden.attr('class', 'hidden');
interface_hidden.attr('id', 'interfaceHidden'+interface_id);
interface_hidden.data('interface-id', interface_id);
......
return interface_hidden;
}
function update_interface_row(row, modal_window) {
row.find('.type').html(modal_window.find('.interface_type option:selected').text());
row.find('.identifier').html(modal_window.find('.interface_identifier').val());
row.find('.mac').html(modal_window.find('.interface_mac').val());
function fqdn(name, domain) {
if (!name || !domain)
return ""
else
return name + '.' + domain;
}
function update_interface_row(row, interface_form) {
row.find('.type').html(interface_form.find('.interface_type option:selected').text());
row.find('.identifier').html(interface_form.find('.interface_identifier').val());
row.find('.mac').html(interface_form.find('.interface_mac').val());
row.find('.ip').html(interface_form.find('.interface_ip').val());
var flags = '', primary_class = '', provision_class = '';
if (interface_form.find('.interface_primary').is(':checked'))
primary_class = 'active'
if (interface_form.find('.interface_provision').is(':checked'))
provision_class = 'active'
if (primary_class == '' && provision_class == '')
row.find('.removeInterface').removeClass('disabled');
else
row.find('.removeInterface').addClass('disabled');
flags += '<i class="glyphicon glyphicon glyphicon-tag primary-flag '+ primary_class +'" title="" data-original-title="'+ __('Primary') +'"></i>';
flags += '<i class="glyphicon glyphicon glyphicon-hdd provision-flag '+ provision_class +'" title="" data-original-title="'+ __('Provisioning') +'"></i>';
row.find('.flags').html(flags);
row.find('.fqdn').html(fqdn(
interface_form.find('.interface_name').val(),
interface_form.find('.interface_domain option:selected').text()
));
$('.primary-flag').tooltip();
$('.provision-flag').tooltip();
}
function update_interface_table() {
$.each(active_interface_forms(), function(index, form) {
var interface_id = $(form).data('interface-id');
var interface_row = get_interface_row(interface_id);
var interface_hidden = get_interface_hidden(interface_id)
update_interface_row(interface_row, interface_hidden);
})
}
function active_interface_forms() {
return $.grep($('#interfaceForms > div'), function(f) {
var flag = $(f).find('.destroyFlag').val();
return (flag == false || flag == undefined);
});
}
function confirm_flag_change(element, element_selector, massage) {
if (!$(element).is(':checked'))
return;
var this_interface_id = $('#interfaceModal').data('current-id');
var other_selected;
other_selected = $(active_interface_forms()).find(element_selector + ':checked').closest('fieldset');
other_selected = $.grep(other_selected, function(i) {
return ($(i).parent().data('interface-id') != this_interface_id);
});
if (other_selected.length > 0) {
return confirm(massage);
}
}
function primary_nic_form() {
return $(active_interface_forms()).find('.interface_primary:checked').closest('fieldset');
}
$(document).on('click', '.interface_primary', function () {
var confirmed = confirm_flag_change(this, '.interface_primary',
__("Some other interface is already set as primary. Are you sure you want to use this one instead?")
);
if (confirmed) {
// preset dns name from host name if it's blank
var name = $(this).closest('fieldset').find('.interface_name');
if (name.val().length == 0)
name.val($('#host_name').val());
}
return confirmed;
});
$(document).on('click', '.interface_provision', function () {
return confirm_flag_change(this, '.interface_provision',
__("Some other interface is already set as provisioning. Are you sure you want to use this one instead?")
);
});
$(document).on('change', '#host_name', function () {
// copy host name to the primary interface's name
primary_nic_form().find('.interface_name').val($(this).val());
update_interface_table();
update_fqdn();
});
$(document).on('click', '.primary-flag', function () {
var interface_id = $(this).closest('tr').data('interface-id');
$('#interfaceForms .interface_primary:checked').prop('checked', false);
get_interface_hidden(interface_id).find('.interface_primary').prop('checked', true);
sync_primary_name(true);
update_interface_table();
update_fqdn();
});
$(document).on('click', '.provision-flag', function () {
var interface_id = $(this).closest('tr').data('interface-id');
$('#interfaceForms .interface_provision:checked').prop('checked', false);
get_interface_hidden(interface_id).find('.interface_provision').prop('checked', true);
update_interface_table();
});
function update_fqdn() {
var host_name = $('#host_name').val();
var domain_name = primary_nic_form().find('.interface_domain option:selected').text();
var name = fqdn(host_name, domain_name)
if (name.length > 0)
name = "| " + name
$('#hostFQDN').text(name);
}
app/assets/stylesheets/application.scss
float: none;
}
#interfaceModal .modal-dialog {
min-width: 1000px
#interfaceModal {
.modal-dialog {
min-width: 1000px;
}
.modal-body {
max-height: none;
}
}
#addInterface {
......
#interfaceList.table {
border-collapse: separate;
border-width: 0 0 1px 0;
border-width: 1px 0 1px 0;
tr {
th:first-child,
......
border-bottom: 1px solid #a94442;
}
}
.primary-flag,
.provision-flag {
cursor: pointer;
color: #bbb;
margin: 2px;
&:hover {
color: #999;
}
&.active {
color: black;
&:hover {
color: #428bca;
}
}
}
}
.autocomplete-input {
display:inline-block !important;
}
.glyphicon.nic-flag {
margin: 2px;
}
.lookup-keys-container{
li .close{
app/controllers/hosts_controller.rb
before_filter :ajax_request, :only => AJAX_REQUESTS
before_filter :find_resource, :only => [:show, :clone, :edit, :update, :destroy, :puppetrun, :review_before_build,
:setBuild, :cancelBuild, :power, :overview, :bmc, :vm,
:runtime, :resources, :templates, :ipmi_boot, :console,
:runtime, :resources, :templates, :nics, :ipmi_boot, :console,
:toggle_manage, :pxe_config, :storeconfig_klasses, :disassociate]
before_filter :taxonomy_scope, :only => [:new, :edit] + AJAX_REQUESTS
......
load_vars_for_ajax
flash[:warning] = _("The marked fields will need reviewing")
@host.valid?
render :action => :new
end
def create
......
end
end
def interfaces
@host = Host.new params[:host]
render :partial => "interfaces_tab"
end
def hostgroup_or_environment_selected
Taxonomy.as_taxonomy @organization, @location do
if params['host']['environment_id'].present? || params['host']['hostgroup_id'].present?
......
process_ajax_error exception, 'fetch templates information'
end
def nics
render :partial => 'nics'
rescue ActionView::Template::Error => exception
process_ajax_error exception, 'fetch interfaces information'
end
def ipmi_boot
device = params[:ipmi_device]
device_id = BOOT_DEVICES.stringify_keys[device.downcase] || device
......
def action_permission
case params[:action]
when 'clone', 'externalNodes', 'overview', 'bmc', 'vm', 'runtime', 'resources', 'templates',
when 'clone', 'externalNodes', 'overview', 'bmc', 'vm', 'runtime', 'resources', 'templates', 'nics',
'pxe_config', 'storeconfig_klasses', 'active', 'errors', 'out_of_sync', 'pending', 'disabled'
:view
when 'puppetrun', 'multiple_puppetrun', 'update_multiple_puppetrun'
app/controllers/interfaces_controller.rb
# {"new_1405068143746"=>
# {"_destroy"=>"false", "type"=>"Nic::BMC", "mac"=>"", "name"=>"", "domain_id"=>"", "ip"=>""}}}}
def new
@host = Host.new params[:host]
attributes = params[:host].fetch(:interfaces_attributes, {})
@key, attributes = attributes.first
raise Foreman::Exception, 'Missing attributes for interface' if @key.blank?
app/controllers/subnets_controller.rb
location = params[:location_id].blank? ? nil : Location.find(params[:location_id])
Taxonomy.as_taxonomy organization, location do
not_found and return unless (subnet = Subnet.authorized(:view_subnets).find(s))
if (ip = subnet.unused_ip(params[:host_mac]))
if (ip = subnet.unused_ip(params[:host_mac], params[:taken_ips]))
render :json => {:ip => ip}
else
# we don't want any failures if we failed to query our proxy
app/controllers/unattended_controller.rb
end
def find_host_by_spoof
host = Host.find_by_ip(params.delete('spoof')) if params['spoof'].present?
host = Nic::Base.primary.find_by_ip(params.delete('spoof')).try(:host) if params['spoof'].present?
host ||= Host.find(params.delete('hostname')) if params['hostname'].present?
@spoof = host.present?
host
......
end
end
# we try to match first based on the MAC, falling back to the IP
Host.where(mac_list.empty? ? { :ip => ip } : ["lower(mac) IN (?)", mac_list]).first
# host is readonly because of association so we reload it if we find it
host = Host.joins(:primary_interface).where(mac_list.empty? ? {:nics => {:ip => ip}} : ["lower(nics.mac) IN (?)", mac_list]).first
host ? Host.find(host.id) : nil
end
def allowed_to_install?
app/helpers/hosts_and_hostgroups_helper.rb
:help_inline => _("Use this puppet server as an initial Puppet Server or to execute puppet runs") }
end
def realm_field(f)
# Don't show this if we have no Realms, otherwise always include blank
# so the user can choose not to use a Realm on this host
return if Realm.count == 0
return unless (SETTINGS[:unattended] == true) && @host.managed
select_f(f, :realm_id,
Realm.with_taxonomy_scope_override(@location, @organization).authorized(:view_realms),
:id, :to_label,
{ :include_blank => true },
{ :help_inline => :indicator }
).html_safe
end
def interesting_klasses(obj)
classes = obj.all_puppetclasses
smart_vars = LookupKey.reorder('').where(:puppetclass_id => classes.map(&:id)).group(:puppetclass_id).count
app/helpers/hosts_helper.rb
include ComputeResourcesVmsHelper
include BmcHelper
def nic_provider_attributes_exist?(host)
return false unless host.compute_resource
compute_resource_name = host.compute_resource.provider_friendly_name.downcase
real_path = File.join(Rails.root, 'app', 'views', 'compute_resources_vms', 'form', compute_resource_name, '_network.html.erb')
File.exist?(real_path)
end
def nic_provider_partial(host)
return nil unless host.compute_resource
compute_resource_name = host.compute_resource.provider_friendly_name.downcase
"compute_resources_vms/form/#{compute_resource_name}/network"
end
def host_taxonomy_select(f, taxonomy)
taxonomy_id = "#{taxonomy.to_s.downcase}_id"
selected_taxonomy = @host.new_record? ? taxonomy.current.try(:id) : @host.send(taxonomy_id)
......
select_opts, html_opts
end
def new_host_title
t = _("New Host")
title(t, (t + ' <span id="hostFQDN"></span>').html_safe)
end
def flags_for_nic(nic)
flags = ""
flags += "<i class=\"nic-flag glyphicon glyphicon glyphicon-tag\" title=\"#{_('Primary')}\"></i>" if nic.primary?
flags += "<i class=\"nic-flag glyphicon glyphicon glyphicon-hdd\" title=\"#{_('Provisioning')}\"></i>" if nic.provision?
flags.html_safe
end
def last_report_column(record)
time = record.last_report? ? _("%s ago") % time_ago_in_words(record.last_report): ""
link_to_if_authorized(time,
......
)
end
# we ignore interfaces.conflict because they are always registered in host errors as well
def conflict_objects(errors)
errors.keys.map(&:to_s).grep(/conflict$/).map(&:to_sym)
errors.keys.map(&:to_s).select { |key| key =~ /conflict$/ && key != 'interfaces.conflict' }.map(&:to_sym)
end
def has_conflicts?(errors)
......
return '' if nic.new_record?
if nic.link
status = '<i class="glyphicon glyphicon glyphicon-arrow-up interface-up" title="'+ _('Up') +'"></i>'
status = '<i class="glyphicon glyphicon glyphicon-arrow-up interface-up" title="'+ _('Interface is up') +'"></i>'
else
status = '<i class="glyphicon glyphicon glyphicon-arrow-down interface-down" title="'+ _('Down') +'"></i>'
status = '<i class="glyphicon glyphicon glyphicon-arrow-down interface-down" title="'+ _('Interface is down') +'"></i>'
end
status.html_safe
end
def interface_flags(nic)
primary_class = nic.primary? ? "active" : ""
provision_class = nic.provision? ? "active" : ""
status = "<i class=\"glyphicon glyphicon glyphicon-tag primary-flag #{primary_class}\" title=\"#{_('Primary')}\"></i>"
status += "<i class=\"glyphicon glyphicon glyphicon-hdd provision-flag #{provision_class}\" title=\"#{_('Provisioning')}\"></i>"
status.html_safe
end
def build_state(build)
build.state ? 'warning' : 'danger'
end
app/models/compute_resource.rb
:image_id
end
def interfaces_attrs_name
"interfaces_attributes"
end
# returns a new fog server instance
def new_vm(attr = {})
test_connection
app/models/compute_resources/foreman/model/libvirt.rb
super.merge({:mac => :mac})
end
def interfaces_attrs_name
"nics_attributes"
end
def capabilities
[:build, :image]
end
app/models/compute_resources/foreman/model/ovirt.rb
end
def associated_host(vm)
Host.authorized(:view_hosts, Host).where(:mac => vm.interfaces.map { |i| i.mac }).first
Host.authorized(:view_hosts, Host).
joins(:primary_interface).
where(:nics => {:primary => true}).
where('nics.mac' => vm.interfaces.map { |i| i.mac }).
first
end
def self.provider_friendly_name
app/models/concerns/destroy_flag.rb
# DestroyFlag adds a flag and a corresponding reader method to any active record
# during deletion. The flag is set to true using before_destroy callback so during
# complicated association deletions you can check whether the deletion includes
# the object, e.g. we normally prevent deletion of primary interface in it's
# before filter but we have to allow it when we delete associated host.
#
# class Host
# include DestroyFlag
# end
#
# class Nic
# belongs_to :host
# before_destroy :keep_primary, :if => Proc.new { |nic| nic.primary? }
#
# def keep_primary
# unless host.being_destroyed? # Here we use being_destroyed? flag
# raise 'we can not delete primary'
# end
# end
# end
#
module DestroyFlag
extend ActiveSupport::Concern
def being_destroyed?
@_active_record_being_destroyed
end
included do
attr_accessor :_active_record_being_destroyed
before_destroy { |record| record._active_record_being_destroyed = true }
end
end
app/models/concerns/fog_extensions/libvirt/server.rb
_("%{cpus} CPUs and %{memory} memory") % {:cpus => cpus, :memory => number_to_human_size(memory.to_i)}
end
# Other Fog CRs use .interfaces as the accessor, but libvirt does not
def interfaces
nics
end
def select_nic(fog_nics, nic)
nic_attrs = nic.compute_attributes
match = fog_nics.detect { |fn| fn.network == nic_attrs['network'] } # grab any nic on the same network
match ||= fog_nics.detect { |fn| fn.bridge == nic_attrs['bridge'] } # no network? try a bridge...
match
end
end
end
end
app/models/concerns/fog_extensions/ovirt/server.rb
_("%{cores} Cores and %{memory} memory") % {:cores => cores, :memory => number_to_human_size(memory.to_i)}
end
def select_nic(fog_nics, nic)
fog_nics.detect {|fn| fn.network == nic.compute_attributes['network']} # grab any nic on the same network
end
end
end
end
app/models/concerns/fog_extensions/vsphere/server.rb
scsi_controller.type
end
def select_nic(fog_nics, nic)
fog_nics.detect {|fn| fn.network == nic.compute_attributes['network']} # grab any nic on the same network
end
end
end
end
app/models/concerns/host_common.rb
belongs_to :ptable
belongs_to :puppet_proxy, :class_name => "SmartProxy"
belongs_to :puppet_ca_proxy, :class_name => "SmartProxy"
belongs_to :domain, :counter_cache => counter_cache
belongs_to :realm, :counter_cache => counter_cache
belongs_to :subnet
belongs_to :compute_profile
before_save :check_puppet_ca_proxy_is_required?, :crypt_root_pass
app/models/concerns/host_template_helpers.rb
protocol = config.scheme || 'http'
port = config.port || request.port
host = config.host || request.host
path = config.path
@host ||= self
proxy = @host.try(:subnet).try(:tftp)
......
host = uri.host
port = uri.port
protocol = uri.scheme
path = config.path
end
url_for :only_path => false, :controller => "/unattended", :action => action,
:protocol => protocol, :host => host, :port => port,
:protocol => protocol, :host => host, :port => port, :script_name => path,
:token => (@host.token.value unless @host.token.nil?)
end
app/models/concerns/hostext/search.rb
scoped_search :on => :name, :complete_value => true, :default_order => true
scoped_search :on => :last_report, :complete_value => true, :only_explicit => true
scoped_search :on => :ip, :complete_value => true
scoped_search :on => :comment, :complete_value => true
scoped_search :on => :enabled, :complete_value => {:true => true, :false => false}, :rename => :'status.enabled'
scoped_search :on => :managed, :complete_value => {:true => true, :false => false}
......
scoped_search :in => :compute_resource, :on => :name, :complete_value => true, :rename => :compute_resource
scoped_search :in => :compute_resource, :on => :id, :complete_enabled => false, :rename => :compute_resource_id, :only_explicit => true
scoped_search :in => :image, :on => :name, :complete_value => true
scoped_search :in => :operatingsystem, :on => :name, :complete_value => true, :rename => :os
scoped_search :in => :operatingsystem, :on => :description, :complete_value => true, :rename => :os_description
scoped_search :in => :operatingsystem, :on => :title, :complete_value => true, :rename => :os_title
......
scoped_search :in => :operatingsystem, :on => :minor, :complete_value => true, :rename => :os_minor
scoped_search :in => :operatingsystem, :on => :id, :complete_enabled => false,:rename => :os_id, :only_explicit => true
scoped_search :in => :primary_interface, :on => :ip, :complete_value => true
scoped_search :in => :puppetclasses, :on => :name, :complete_value => true, :rename => :class, :only_explicit => true, :operators => ['= ', '~ '], :ext_method => :search_by_puppetclass
scoped_search :in => :fact_values, :on => :value, :in_key=> :fact_names, :on_key=> :name, :rename => :facts, :complete_value => true, :only_explicit => true
scoped_search :in => :search_parameters, :on => :value, :on_key=> :name, :complete_value => true, :rename => :params, :ext_method => :search_by_params, :only_explicit => true
......
if SETTINGS[:unattended]
scoped_search :in => :subnet, :on => :network, :complete_value => true, :rename => :subnet
scoped_search :in => :subnet, :on => :name, :complete_value => true, :rename => 'subnet.name'
scoped_search :on => :mac, :complete_value => true
scoped_search :on => :uuid, :complete_value => true
scoped_search :on => :build, :complete_value => {:true => true, :false => false}
scoped_search :on => :installed_at, :complete_value => true, :only_explicit => true
scoped_search :in => :provision_interface, :on => :mac, :complete_value => true
scoped_search :in => :operatingsystem, :on => :name, :complete_value => true, :rename => :os
scoped_search :in => :operatingsystem, :on => :description, :complete_value => true, :rename => :os_description
scoped_search :in => :operatingsystem, :on => :title, :complete_value => true, :rename => :os_title
scoped_search :in => :operatingsystem, :on => :major, :complete_value => true, :rename => :os_major
scoped_search :in => :operatingsystem, :on => :minor, :complete_value => true, :rename => :os_minor
scoped_search :in => :operatingsystem, :on => :id, :complete_value => false,:rename => :os_id, :complete_enabled => false
end
if SETTINGS[:login]
app/models/concerns/orchestration.rb
end
def on_destroy
errors.empty? ? process(:queue) : false
errors.empty? ? process(:queue) : rollback
end
def rollback
......
update_cache
begin
task.status = execute({:action => task.action}) ? "completed" : "failed"
rescue Net::Conflict => e
task.status = "conflict"
record_conflicts << e
add_conflict(e)
failure e.message, nil, :conflict
rescue => e
task.status = "failed"
......
rollback
end
def add_conflict(e)
@record_conflicts << e
end
def execute(opts = {})
obj, met = opts[:action]
rollback = opts[:rollback] || false
......
end
# we keep the before update host object in order to compare changes
def setup_clone
def setup_clone(&block)
return if new_record?
@old = dup
for key in (changed_attributes.keys - ["updated_at"])
@old.send "#{key}=", changed_attributes[key]
# At this point the old cached bindings may still be present so we force an AR association reload
# This logic may not work or be required if we switch to Rails 3
if (match = key.match(/\A(.*)_id\Z/))
name = match[1].to_sym
next if name == :owner # This does not work for the owner association even from the console
self.send(name, true) if (send(name) and send(name).id != @attributes[key])
old.send(name, true) if (old.send(name) and old.send(name).id != old.attributes[key])
end
@old = setup_object_clone(self, &block)
end
def setup_object_clone(object)
clone = object.dup
yield(clone) if block_given?
# we can't assign using #attributes= because of mass-assign protected attributes (e.g. type)
for key in (object.changed_attributes.keys - ["updated_at"])
clone.send "#{key}=", object.changed_attributes[key]
end
clone
end
def orchestration_errors?
app/models/concerns/orchestration/compute.rb
def setCompute
logger.info "Adding Compute instance for #{name}"
add_interfaces_to_compute_attrs
self.vm = compute_resource.create_vm compute_attributes.merge(:name => Setting[:use_shortname_for_vms] ? shortname : name)
rescue => e
failure _("Failed to create a compute %{compute_resource} instance %{name}: %{message}\n ") % { :compute_resource => compute_resource, :name => name, :message => e.message }, e.backtrace
......
def setComputeDetails
if vm
attrs = compute_resource.provided_attributes
normalize_addresses if attrs.keys.include?(:mac) or attrs.keys.include?(:ip)
attrs.each do |foreman_attr, fog_attr |
# we can't ensure uniqueness of #foreman_attr using normal rails validations as that gets in a later step in the process
# therefore we must validate its not used already in our db.
value = vm.send(fog_attr)
value ||= find_address if foreman_attr == :ip
self.send("#{foreman_attr}=", value)
if value.blank? or (other_host = Host.send("find_by_#{foreman_attr}", value))
delCompute
return failure("#{foreman_attr} #{value} is already used by #{other_host}") if other_host
return failure("#{foreman_attr} value is blank!")
if foreman_attr == :mac
#TODO, do we need handle :ip as well? for openstack / ec2 we only set a single
# interface (so host.ip will be fine), and we'd need to rethink #find_address :/
return false unless match_macs_to_nics(fog_attr)
else
value = vm.send(fog_attr)
value ||= find_address if foreman_attr == :ip
self.send("#{foreman_attr}=", value)
# validate_foreman_attr handles the failure msg, so we just bubble
# the false state up the stack
return false unless validate_foreman_attr(value,Host,foreman_attr)
end
end
true
......
false
end
def add_interfaces_to_compute_attrs
# We now store vm fields in the Nic model, so we need to add them to
# compute_attrs before creating the vm
attrs_name = compute_resource.interfaces_attrs_name
return unless compute_attributes[attrs_name].blank?
compute_attributes[attrs_name] = {}
self.interfaces.each do |nic|
compute_attributes[attrs_name][nic.object_id.to_s] = nic.compute_attributes
end
end
def validate_foreman_attr(value,object,attr)
# we can't ensure uniqueness of #foreman_attr using normal rails
# validations as that gets in a later step in the process
# therefore we must validate its not used already in our db.
if value.blank?
delCompute
return failure("#{attr} value is blank!")
elsif (other_object = object.send("find_by_#{attr}", value))
delCompute
return failure("#{attr} #{value} is already used by #{other_object}")
end
true
end
def match_macs_to_nics(fog_attr)
# mac/ip are properties of the NIC, and there may be more than one,
# so we need to loop. First store the nics returned from Fog in a local
# array so we can delete from it safely
fog_nics = vm.interfaces.dup
self.interfaces.each do |nic|
selected_nic = vm.select_nic(fog_nics, nic)
next if selected_nic.nil? # found no matching fog nic for this Foreman nic, move on
mac = selected_nic.send(fog_attr)
logger.debug "Orchestration::Compute: nic #{nic.inspect} assigned to #{selected_nic.inspect}"
nic.mac = mac
fog_nics.delete(selected_nic) # don't use the same fog nic twice
# In future, we probably want to skip validation of macs/ips on the Nic
# macs can be duplicated if we are creating bonds
# ips can be duplicated if we have isolated subnets (needs an update in the Subnet model first)
# For now, we scope to physical devices only for the validations
# validate_foreman_attr handles the failure msg, so we just bubble
# the false state up the stack
return false unless validate_foreman_attr(mac,Nic::Base.physical,:mac)
end
true
end
end
app/models/concerns/orchestration/dhcp.rb
end
def dhcp?
hostname.present? && ip_available? && mac_available? && !subnet.nil? && subnet.dhcp? && managed?
hostname.present? && ip_available? && mac_available? && !subnet.nil? && subnet.dhcp? && host.managed? && managed?
end
def dhcp_record
app/models/concerns/orchestration/dns.rb
end
def dns?
hostname.present? and ip_available? and !domain.nil? and !domain.proxy.nil? and managed?
hostname.present? && ip_available? && !domain.nil? && !domain.proxy.nil? && host.managed? && managed?
end
def reverse_dns?
hostname.present? and ip_available? and !subnet.nil? and subnet.dns? and managed?
hostname.present? && ip_available? && !subnet.nil? && subnet.dns? && host.managed? && managed?
end
def dns_a_record
app/models/concerns/orchestration/tftp.rb
end
def tftp?
!!(subnet && subnet.tftp?) && (operatingsystem && operatingsystem.pxe_variant) && managed? && pxe_build?
provision? && !!(subnet && subnet.tftp?) && host.managed? && (host.operatingsystem && host.operatingsystem.pxe_variant) && managed? && pxe_build?
end
def tftp
subnet.tftp_proxy(:variant => operatingsystem.pxe_variant) if tftp?
subnet.tftp_proxy(:variant => host.operatingsystem.pxe_variant) if tftp?
end
protected
......
# Adds the host to the forward and reverse TFTP zones
# +returns+ : Boolean true on success
def setTFTP
logger.info "Add the TFTP configuration for #{name}"
logger.info "Add the TFTP configuration for #{host.name}"
tftp.set mac, :pxeconfig => generate_pxe_template
end
# Removes the host from the forward and reverse TFTP zones
# +returns+ : Boolean true on success
def delTFTP
logger.info "Delete the TFTP configuration for #{name}"
logger.info "Delete the TFTP configuration for #{host.name}"
tftp.delete mac
end
def setTFTPBootFiles
logger.info "Fetching required TFTP boot files for #{name}"
logger.info "Fetching required TFTP boot files for #{host.name}"
valid = true
operatingsystem.pxe_files(medium, architecture, self).each do |bootfile_info|
host.operatingsystem.pxe_files(host.medium, host.architecture, self).each do |bootfile_info|
for prefix, path in bootfile_info do
valid = false unless tftp.fetch_boot_file(:prefix => prefix.to_s, :path => path)
end
......
def validate_tftp
return unless tftp?
return unless operatingsystem
return unless host.operatingsystem
return if Rails.env == "test"
if configTemplate({:kind => operatingsystem.template_kind}).nil? and configTemplate({:kind => "iPXE"}).nil?
failure _("No %{template_kind} templates were found for this host, make sure you define at least one in your %{os} settings") % { :template_kind => operatingsystem.template_kind, :os => os }
if host.configTemplate({:kind => host.operatingsystem.template_kind}).nil? && host.configTemplate({:kind => "iPXE"}).nil?
failure _("No %{template_kind} templates were found for this host, make sure you define at least one in your %{os} settings") %
{ :template_kind => host.operatingsystem.template_kind, :os => host.os }
end
end
def generate_pxe_template
# this is the only place we generate a template not via a web request
# therefore some workaround is required to "render" the template.
@kernel = os.kernel(arch)
@initrd = os.initrd(arch)
@kernel = host.os.kernel(host.arch)
@initrd = host.os.initrd(host.arch)
# work around for ensuring that people can use @host as well, as tftp templates were usually confusing.
@host = self
@host = self.host
if build?
pxe_render configTemplate({:kind => os.template_kind})
pxe_render host.configTemplate({:kind => host.os.template_kind})
else
if os.template_kind == "PXEGrub"
if host.os.template_kind == "PXEGrub"
pxe_render ConfigTemplate.find_by_name("PXEGrub default local boot")
else
pxe_render ConfigTemplate.find_by_name("PXELinux default local boot")
end
end
rescue => e
failure _("Failed to generate %{template_kind} template: %{e}") % { :template_kind => os.template_kind, :e => e }
failure _("Failed to generate %{template_kind} template: %{e}") % { :template_kind => host.os.template_kind, :e => e }
end
def queue_tftp
return unless tftp? and errors.empty?
return unless tftp? && no_errors
# Jumpstart builds require only minimal tftp services. They do require a tftp object to query for the boot_server.
return true if jumpstart?
return true if host.jumpstart?
new_record? ? queue_tftp_create : queue_tftp_update
end
......
def queue_tftp_update
set_tftp = false
# we switched build mode
set_tftp = true if old.build? != build?
set_tftp = true if old.host.build? != host.build?
# medium or arch changed
set_tftp = true if old.medium.try(:id) != medium.try(:id) or old.arch.try(:id) != arch.try(:id)
set_tftp = true if old.host.medium.try(:id) != host.medium.try(:id) or old.host.arch.try(:id) != host.arch.try(:id)
# operating system changed
set_tftp = true if os and old.os and (old.os.name != os.name or old.os.try(:id) != os.try(:id))
set_tftp = true if host.os and old.host.os and (old.host.os.name != host.os.name or old.host.os.try(:id) != host.os.try(:id))
# MAC address changed
if mac != old.mac
set_tftp = true
......
end
def queue_tftp_destroy
return unless tftp? and errors.empty?
return true if jumpstart?
return unless tftp? && no_errors
return true if host.jumpstart?
queue.create(:name => _("TFTP Settings for %s") % self, :priority => 20,
:action => [self, :delTFTP])
end
def no_errors
errors.empty? && host.errors.empty?
end
end
app/models/domain.rb
audited :allow_mass_assignment => true
validates_lengths_from_database
has_many_hosts
has_many :hostgroups
#order matters! see https://github.com/rails/rails/issues/670
before_destroy EnsureNotUsedBy.new(:hosts, :hostgroups, :subnets)
before_destroy EnsureNotUsedBy.new(:interfaces, :hostgroups, :subnets)
has_many :subnet_domains, :dependent => :destroy
has_many :subnets, :through => :subnet_domains
belongs_to :dns, :class_name => "SmartProxy"
has_many :domain_parameters, :dependent => :destroy, :foreign_key => :reference_id, :inverse_of => :domain
has_many :parameters, :dependent => :destroy, :foreign_key => :reference_id, :class_name => "DomainParameter"
has_many :interfaces, :class_name => 'Nic::Base'
has_many :primary_interfaces, :class_name => 'Nic::Base', :conditions => { :primary => true }
has_many :hosts, :through => :interfaces
has_many :primary_hosts, :through => :primary_interfaces, :source => :host
accepts_nested_attributes_for :domain_parameters, :allow_destroy => true
include ParameterValidators
......
['name']
end
# overwrite method in taxonomix, since domain is not direct association of host anymore
def used_taxonomy_ids(type)
return [] if new_record?
Host::Base.joins(:primary_interface).where(:nics => {:domain_id => id}).pluck(type).compact.uniq
end
end
app/models/host/base.rb
include Authorizable
include CounterCacheFix
include Parameterizable::ByName
include DestroyFlag
self.table_name = :hosts
extend FriendlyId
......
has_many :fact_names, :through => :fact_values
has_many :interfaces, :dependent => :destroy, :inverse_of => :host, :class_name => 'Nic::Base',
:foreign_key => :host_id, :order => 'identifier'
accepts_nested_attributes_for :interfaces, :reject_if => lambda { |a| a[:mac].blank? }, :allow_destroy => true
has_one :primary_interface, :class_name => 'Nic::Base', :foreign_key => 'host_id',
:conditions => { :primary => true }
has_one :provision_interface, :class_name => 'Nic::Base', :foreign_key => 'host_id',
:conditions => { :provision => true }
has_one :domain, :through => :primary_interface
has_one :subnet, :through => :primary_interface
accepts_nested_attributes_for :interfaces, :allow_destroy => true
alias_attribute :hostname, :name
before_validation :normalize_name
validates :name, :presence => true, :uniqueness => true, :format => {:with => Net::Validations::HOST_REGEXP}
validates :owner_type, :inclusion => { :in => OWNER_TYPES,
:allow_blank => true,
:message => (_("Owner type needs to be one of the following: %s") % OWNER_TYPES.join(', ')) }
validate :host_has_required_interfaces
# primary interface is mandatory because of delegated methods so we build it if it's missing
# similar for provision interface
# we can't set name attribute until we have primary interface so we don't pass it to super
# initializer and we set name when we are sure that we have primary interface
# we can't create primary interface before calling super because args may contain nested
# interface attributes
def initialize(*args)
primary_interface_attrs = [:name, :ip, :mac,
:subnet, :subnet_id, :subnet_name,
:domain, :domain_id, :domain_name,
:lookup_values_attributes]
values_for_primary_interface = {}
new_attrs = args.shift
unless new_attrs.nil?
new_attrs = new_attrs.with_indifferent_access
primary_interface_attrs.each do |attr|
values_for_primary_interface[attr] = new_attrs.delete(attr) if new_attrs.has_key?(attr)
end
args.unshift(new_attrs.to_hash)
end
super(*args)
self.interfaces.build(:primary => true, :type => 'Nic::Managed') if self.primary_interface.nil?
self.primary_interface.provision = true if self.provision_interface.nil?
values_for_primary_interface.each do |name, value|
self.send "#{name}=", value
end
end
delegate :ip, :mac,
:subnet, :subnet_id, :subnet_name,
:domain, :domain_id, :domain_name,
:hostname,
:to => :primary_interface, :allow_nil => true
delegate :name=, :ip=, :mac=, :subnet=, :subnet_id=, :subnet_name=,
:domain=, :domain_id=, :domain_name=, :to => :primary_interface
attr_writer :updated_virtuals
def updated_virtuals
......
end
def set_interfaces(parser)
# if host has no information in primary interface we try to match it and update it
# instead of creating new interface, suggested primary interface mac and identifier
# is saved to primary interface so we match it in updating code below
if !self.managed? && self.primary_interface.mac.blank? && self.primary_interface.identifier.blank?
identifier, values = parser.suggested_primary_interface(self)
self.primary_interface.mac = Net::Validations.normalize_mac(values[:macaddress])
self.primary_interface.identifier = identifier
self.primary_interface.save!
end
parser.interfaces.each do |name, attributes|
begin
macaddress = Net::Validations.normalize_mac(attributes[:macaddress])
......
# if we want to update the device it must have same identifier
base = base.virtual.where(:identifier => name)
else
# for physical devices we ignore primary interface which is updated by other facts
# we just update its name and log it
if macaddress != Net::Validations.normalize_mac(self.mac)
base = base.physical
else
logger.debug "Skipping #{name} since it is primary interface of host #{self.name}"
old = self.primary_interface
self.update_attribute :primary_interface, name
update_virtuals(old, name) if old != name && old.present?
next
end
base = base.physical
end
iface = base.first || interface_class(name).new(:managed => false)
......
iface.provider ||= 'IPMI'
set_interface(ipmi, 'ipmi', iface)
end
self.interfaces.reload
end
def facts_hash
......
comparison_object.id == id
end
def normalize_name
self.name = Net::Validations.normalize_hostname(name) if self.name.present?
end
def set_taxonomies(facts)
['location', 'organization'].each do |taxonomy|
next unless SETTINGS["#{taxonomy.pluralize}_enabled".to_sym]
......
@overwrite = value.to_s == "true"
end
def has_primary_interface?
self.primary_interface.present?
def primary_interface
get_interface_by_flag(:primary)
end
def provision_interface
get_interface_by_flag(:provision)
end
def managed_interfaces
......
self.interfaces.is_managed.where(:identifier => identifiers).all
end
def reload(*args)
drop_primary_interface_cache
drop_provision_interface_cache
super
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff