foreman_discovery 15.0.2 → 16.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/discovery_rules_controller.rb +1 -1
  3. data/app/controllers/concerns/foreman/controller/discovered_extensions.rb +4 -0
  4. data/app/controllers/discovered_hosts_controller.rb +6 -3
  5. data/app/controllers/discovery_rules_controller.rb +1 -1
  6. data/app/models/discovery_rule.rb +1 -1
  7. data/app/models/host/discovered.rb +49 -31
  8. data/app/models/host/managed_extensions.rb +2 -2
  9. data/app/models/setting/discovered.rb +28 -33
  10. data/app/services/foreman_discovery/fact_parser.rb +1 -1
  11. data/app/services/foreman_discovery/host_converter.rb +38 -2
  12. data/app/services/foreman_discovery/host_fact_importer.rb +10 -0
  13. data/app/services/foreman_discovery/import_hooks/subnet_and_taxonomy.rb +6 -14
  14. data/app/services/foreman_discovery/node_api/node_resource.rb +1 -0
  15. data/app/services/foreman_discovery/subnet_suggestion.rb +26 -0
  16. data/app/views/foreman_discovery/debian_kexec.erb +3 -2
  17. data/app/views/foreman_discovery/redhat_kexec.erb +2 -1
  18. data/config/routes.rb +2 -0
  19. data/db/migrate/20150512150432_remove_old_discovery_reader_permissions.rb +1 -1
  20. data/db/migrate/20151023144501_regenerate_red_hat_kexec.rb +1 -1
  21. data/db/migrate/20180412124505_add_priority_score_to_discovery_rules.rb +1 -1
  22. data/extra/discover-host +34 -14
  23. data/lib/foreman_discovery/engine.rb +4 -4
  24. data/lib/foreman_discovery/version.rb +1 -1
  25. data/locale/ca/LC_MESSAGES/foreman_discovery.mo +0 -0
  26. data/locale/ca/foreman_discovery.edit.po +122 -93
  27. data/locale/ca/foreman_discovery.po +31 -8
  28. data/locale/de/LC_MESSAGES/foreman_discovery.mo +0 -0
  29. data/locale/de/foreman_discovery.edit.po +125 -96
  30. data/locale/de/foreman_discovery.po +34 -11
  31. data/locale/en/LC_MESSAGES/foreman_discovery.mo +0 -0
  32. data/locale/en/foreman_discovery.edit.po +118 -88
  33. data/locale/en/foreman_discovery.po +27 -4
  34. data/locale/en_GB/LC_MESSAGES/foreman_discovery.mo +0 -0
  35. data/locale/en_GB/foreman_discovery.edit.po +125 -96
  36. data/locale/en_GB/foreman_discovery.po +34 -11
  37. data/locale/es/LC_MESSAGES/foreman_discovery.mo +0 -0
  38. data/locale/es/foreman_discovery.edit.po +124 -95
  39. data/locale/es/foreman_discovery.po +33 -10
  40. data/locale/foreman_discovery.pot +119 -89
  41. data/locale/fr/LC_MESSAGES/foreman_discovery.mo +0 -0
  42. data/locale/fr/foreman_discovery.edit.po +122 -93
  43. data/locale/fr/foreman_discovery.po +31 -8
  44. data/locale/gl/LC_MESSAGES/foreman_discovery.mo +0 -0
  45. data/locale/gl/foreman_discovery.edit.po +120 -91
  46. data/locale/gl/foreman_discovery.po +29 -6
  47. data/locale/it/LC_MESSAGES/foreman_discovery.mo +0 -0
  48. data/locale/it/foreman_discovery.edit.po +120 -91
  49. data/locale/it/foreman_discovery.po +29 -6
  50. data/locale/ja/LC_MESSAGES/foreman_discovery.mo +0 -0
  51. data/locale/ja/foreman_discovery.edit.po +123 -94
  52. data/locale/ja/foreman_discovery.po +32 -9
  53. data/locale/ko/LC_MESSAGES/foreman_discovery.mo +0 -0
  54. data/locale/ko/foreman_discovery.edit.po +122 -93
  55. data/locale/ko/foreman_discovery.po +31 -8
  56. data/locale/pt_BR/LC_MESSAGES/foreman_discovery.mo +0 -0
  57. data/locale/pt_BR/foreman_discovery.edit.po +123 -94
  58. data/locale/pt_BR/foreman_discovery.po +32 -9
  59. data/locale/ru/LC_MESSAGES/foreman_discovery.mo +0 -0
  60. data/locale/ru/foreman_discovery.edit.po +123 -94
  61. data/locale/ru/foreman_discovery.po +32 -9
  62. data/locale/sv_SE/LC_MESSAGES/foreman_discovery.mo +0 -0
  63. data/locale/sv_SE/foreman_discovery.edit.po +121 -92
  64. data/locale/sv_SE/foreman_discovery.po +30 -7
  65. data/locale/zh_CN/LC_MESSAGES/foreman_discovery.mo +0 -0
  66. data/locale/zh_CN/foreman_discovery.edit.po +122 -93
  67. data/locale/zh_CN/foreman_discovery.po +31 -8
  68. data/locale/zh_TW/LC_MESSAGES/foreman_discovery.mo +0 -0
  69. data/locale/zh_TW/foreman_discovery.edit.po +120 -91
  70. data/locale/zh_TW/foreman_discovery.po +29 -6
  71. data/test/facts/bond0-eth0-eth1-active-passive.json +128 -0
  72. data/test/facts/facts_with_lldp_bond_candidate.json +2 -9
  73. data/test/facts/only-ipv6.json +205 -0
  74. data/test/facts/skylake-ipv6.json +223 -0
  75. data/test/functional/api/v2/discovered_hosts_controller_test.rb +1 -0
  76. data/test/functional/api/v2/settings_controller_test.rb +2 -2
  77. data/test/functional/discovered_hosts_controller_test.rb +15 -6
  78. data/test/integration/discovered_hosts_test.rb +6 -11
  79. data/test/test_helper_discovery.rb +12 -0
  80. data/test/unit/discovered_extensions_test.rb +54 -0
  81. data/test/unit/discovery_attribute_set_test.rb +13 -10
  82. data/test/unit/discovery_rule_test.rb +1 -0
  83. data/test/unit/host_discovered_test.rb +32 -29
  84. data/test/unit/managed_extensions_test.rb +2 -0
  85. metadata +39 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7da55b634c6847d6d4694e7c01c05640481de4589267d8b0f8bfad89e18be03e
4
- data.tar.gz: e4321f0bd91cb985955c30f39c2497dc56cd43f7c9452d8bf81e933a4a704dbf
3
+ metadata.gz: accf12de8ae1253e5023ba98c65da7c3af14aa8da4691fe67dd7f18bc5717518
4
+ data.tar.gz: 0dd4c83a875917ccf3933679a31a747d8f559e5195415fab619cc0220bcd97c4
5
5
  SHA512:
6
- metadata.gz: 6b8adac21ff97553128c433c222a45ee8529a1d83ab75c9a0657d7b536320bc0b7420af6562eecdf5c0f1d684b118d64331c8e96158553edf9f5be93d4b6b600
7
- data.tar.gz: 8ee273cf286df977a582413f006ecc7529b47b4507b2be598cde0c9db6fa91f0b6fbc3fcbfd5ff815873622fe5d1b6d3162cd0ba766c029d2635162a4168889a
6
+ metadata.gz: a8fd61670a325106898043ab9533f7512835394a6141139580436e9c8fdd0027ff0aee02c0a53f6d92b5b4535d5bcbcabfb1cd91ddc0fd85ce8a0a54d2cde27e
7
+ data.tar.gz: 90263e8535e3d338ee335a280ff89dafd188d7e92c8f7becb340bc6ede0884bbf4ee915ad158de546c84058240c7338d1d4a736f3124e8c6c57f935862073f83
@@ -55,7 +55,7 @@ module Api
55
55
  param_group :discovery_rule, :as => :update
56
56
 
57
57
  def update
58
- process_response @discovery_rule.update_attributes(discovery_rule_params)
58
+ process_response @discovery_rule.update(discovery_rule_params)
59
59
  end
60
60
 
61
61
  api :DELETE, "/discovery_rules/:id/", N_("Delete a rule")
@@ -41,6 +41,8 @@ module Foreman::Controller::DiscoveredExtensions
41
41
  # trigger the provisioning
42
42
  def perform_auto_provision original_host, rule
43
43
  raise(::Foreman::Exception.new(N_("No hostgroup associated with rule '%s'"), rule)) if rule.hostgroup.nil?
44
+
45
+ logger.debug "Auto-provisioning via rule #{rule} hostgroup #{rule.hostgroup} subnet #{rule.hostgroup.subnet}"
44
46
  host = ::ForemanDiscovery::HostConverter.to_managed(original_host)
45
47
  host.hostgroup_id = rule.hostgroup_id
46
48
  host.comment = "Auto-discovered and provisioned via rule '#{rule.name}'"
@@ -55,6 +57,8 @@ module Foreman::Controller::DiscoveredExtensions
55
57
  # explicitly set all inheritable attributes from hostgroup
56
58
  host.attributes = host.apply_inherited_attributes(hostgroup_id: rule.hostgroup_id)
57
59
  host.set_hostgroup_defaults
60
+ # change subnet and fetch unused IPs
61
+ ::ForemanDiscovery::HostConverter.unused_ip_for_host(host, rule.hostgroup.subnet, rule.hostgroup.subnet6)
58
62
  # save! does not work here
59
63
  if host.save
60
64
  host
@@ -78,6 +78,7 @@ class DiscoveredHostsController < ::ApplicationController
78
78
  def perform_update(host, success_message = nil)
79
79
  Taxonomy.no_taxonomy_scope do
80
80
  ::ForemanDiscovery::HostConverter.set_build_clean_facts(host)
81
+ ::ForemanDiscovery::HostConverter.unused_ip_for_host(host)
81
82
  if host.save
82
83
  success_options = { :success_redirect => host_path(host), :redirect_xhr => request.xhr? }
83
84
  success_options[:success_msg] = success_message if success_message
@@ -198,15 +199,17 @@ class DiscoveredHostsController < ::ApplicationController
198
199
 
199
200
  def setup_host_class_variables
200
201
  if @host.hostgroup
202
+ subnet = @host.hostgroup.subnet || @host.subnet
203
+ subnet6 = @host.hostgroup.subnet6 || @host.subnet6
201
204
  @architecture = @host.hostgroup.architecture
202
205
  @operatingsystem = @host.hostgroup.operatingsystem
203
206
  @environment = @host.hostgroup.environment
204
207
  @domain = @host.hostgroup.domain
205
- @subnet = @host.hostgroup.subnet
208
+ @subnet = subnet
209
+ @subnet6 = subnet6
206
210
  @compute_profile = @host.hostgroup.compute_profile
207
211
  @realm = @host.hostgroup.realm
208
- @host.interfaces.first.assign_attributes(subnet: @subnet,
209
- domain: @domain)
212
+ @host.interfaces.first.assign_attributes(subnet: subnet, subnet6: subnet6, domain: @domain)
210
213
  @host.environment = @environment
211
214
  end
212
215
  end
@@ -28,7 +28,7 @@ class DiscoveryRulesController < ApplicationController
28
28
  end
29
29
 
30
30
  def update
31
- if @discovery_rule.update_attributes(discovery_rule_params)
31
+ if @discovery_rule.update(discovery_rule_params)
32
32
  process_success
33
33
  else
34
34
  process_error
@@ -22,7 +22,7 @@ class DiscoveryRule < ApplicationRecord
22
22
  before_validation :enforce_taxonomy
23
23
 
24
24
  belongs_to :hostgroup
25
- has_many :hosts, :dependent => :nullify
25
+ has_many_hosts :dependent => :nullify
26
26
 
27
27
  scoped_search :on => :name, :complete_value => :true
28
28
  scoped_search :on => :priority, :only_explicit => true
@@ -44,9 +44,13 @@ class Host::Discovered < ::Host::Base
44
44
 
45
45
  # Discovery import workflow:
46
46
  # discovered#import_host ->
47
- # discovered#import_facts -> base#import_facts -> base#parse_facts ->
48
- # discovered#populate_fields_from_facts -> base#populate_fields_from_facts -> base#set_interfaces
49
- # discovered#populate_discovery_fields_from_facts
47
+ # ForemanDiscovery::HostFactImporter#import_facts ->
48
+ # ::HostFactImporter#import_facts ->
49
+ # ::HostFactImporter#parse_facts ->
50
+ # discovered#populate_fields_from_facts ->
51
+ # base#populate_fields_from_facts ->
52
+ # base#set_interfaces ->
53
+ # discovered#populate_discovery_fields_from_facts
50
54
  def self.import_host facts
51
55
  raise(::Foreman::Exception.new(N_("Invalid facts, must be a Hash"))) unless facts.is_a?(Hash) || facts.is_a?(ActionController::Parameters)
52
56
 
@@ -92,19 +96,11 @@ class Host::Discovered < ::Host::Base
92
96
 
93
97
  # and save (interfaces are created via puppet parser extension)
94
98
  host.save(:validate => false) if host.new_record?
95
- raise ::Foreman::Exception.new(N_("Facts could not be imported")) unless host.import_facts(facts)
99
+ importer = ForemanDiscovery::HostFactImporter.new(host)
100
+ raise ::Foreman::Exception.new(N_("Facts could not be imported")) unless importer.import_facts(facts)
96
101
  host
97
102
  end
98
103
 
99
- def import_facts(facts)
100
- # Discovered Hosts won't report in via puppet, so we can use that field to
101
- # record the last time it sent facts...
102
- self.last_report = Time.now
103
- # Set the correct facts type for new foreman facts importing code.
104
- facts[:_type] = :foreman_discovery
105
- super(facts)
106
- end
107
-
108
104
  def setup_clone
109
105
  # Nic::Managed needs this method but Discovered hosts shouldn't
110
106
  # be doing orchestration anyway...
@@ -137,33 +133,55 @@ class Host::Discovered < ::Host::Base
137
133
  subnet.present? && subnet.discovery.present?
138
134
  end
139
135
 
140
- def proxy_url
141
- proxied? ? subnet.discovery.url + "/discovery/#{self.ip}" : "https://#{self.ip}:8443"
136
+ def proxy_url(node_ip)
137
+ proxied? ? subnet.discovery.url + "/discovery/#{node_ip}" : "https://#{node_ip}:8443"
142
138
  end
143
139
 
144
140
  def refresh_facts
145
- facts = ::ForemanDiscovery::NodeAPI::Inventory.new(:url => proxy_url).facter
141
+ facts = ::ForemanDiscovery::NodeAPI::Inventory.new(:url => proxy_url(self.ip)).facter
146
142
  self.class.import_host facts
147
- import_facts facts
143
+ ::ForemanDiscovery::HostFactImporter.new(self).import_facts facts
148
144
  rescue => e
149
145
  ::Foreman::Logging.exception("Unable to get facts from proxy", e)
150
146
  raise ::Foreman::WrappedException.new(e, N_("Could not get facts from proxy %{url}: %{error}"), :url => proxy_url, :error => e)
151
147
  end
152
148
 
153
- def reboot
154
- resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => proxy_url)
155
- resource.reboot
156
- rescue => e
157
- ::Foreman::Logging.exception("Unable to reboot #{name}", e)
158
- raise ::Foreman::WrappedException.new(e, N_("Unable to reboot %{name} via %{url}: %{msg}"), :name => name, :url => proxy_url, :msg => e.to_s)
159
- end
160
-
161
- def kexec json
162
- resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => proxy_url)
163
- resource.kexec json
164
- rescue => e
165
- ::Foreman::Logging.exception("Unable to perform kexec on #{name}", e)
166
- raise ::Foreman::WrappedException.new(e, N_("Unable to perform kexec on %{name} via %{url}: %{msg}"), :name => name, :url => proxy_url, :msg => e.to_s)
149
+ def reboot(old_ip = nil, new_ip = nil)
150
+ # perform the action against the original lease as well as the new reservation
151
+ ips = [old_ip, new_ip, self.ip].compact.uniq
152
+ logger.debug "Performing reboot calls against #{ips.to_sentence}, facts left #{facts.count}"
153
+ ips.each do |next_ip|
154
+ begin
155
+ node_url = proxy_url(next_ip)
156
+ logger.debug "Performing reboot call against #{node_url}"
157
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => node_url)
158
+ return true if resource.reboot
159
+ rescue => e
160
+ msg = N_("Unable to perform reboot on %{name} (%{url}): %{msg}")
161
+ ::Foreman::Logging.exception(msg % { :name => name, :url => node_url, :msg => e.to_s }, e)
162
+ end
163
+ end
164
+ msg = N_("Unable to perform %{action} on %{ips}")
165
+ raise ::Foreman::Exception.new(msg, action: "reboot", ips: ips.to_sentence)
166
+ end
167
+
168
+ def kexec(json, old_ip = nil, new_ip = nil)
169
+ # perform the action against the original lease as well as the new reservation
170
+ ips = [old_ip, new_ip, self.ip].compact.uniq
171
+ logger.debug "Performing kexec calls against #{ips.to_sentence}, #{facts.count} facts left"
172
+ ips.each do |next_ip|
173
+ begin
174
+ node_url = proxy_url(next_ip)
175
+ logger.debug "Performing kexec call against #{node_url}"
176
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => node_url)
177
+ return true if resource.kexec(json)
178
+ rescue => e
179
+ msg = N_("Unable to perform kexec on %{name} (%{url}): %{msg}")
180
+ ::Foreman::Logging.exception(msg % { :name => name, :url => node_url, :msg => e.to_s }, e)
181
+ end
182
+ end
183
+ msg = N_("Unable to perform %{action} on %{ips}")
184
+ raise ::Foreman::Exception.new(msg, action: "kexec", ips: ips.to_sentence)
167
185
  end
168
186
 
169
187
  def self.model_name
@@ -27,7 +27,7 @@ module Host::ManagedExtensions
27
27
  end
28
28
 
29
29
  def setReboot
30
- old.becomes(Host::Discovered).reboot
30
+ old.becomes(Host::Discovered).reboot(old.ip, ip)
31
31
  # It is too late to report error in the post_queue, we catch them and
32
32
  # continue. If flash is implemented for new hosts (http://projects.theforeman.org/issues/10559)
33
33
  # we can report the error to the user perhaps.
@@ -55,7 +55,7 @@ module Host::ManagedExtensions
55
55
  end
56
56
 
57
57
  def setKexec
58
- old.becomes(Host::Discovered).kexec(render_kexec_template.to_json)
58
+ old.becomes(Host::Discovered).kexec(render_kexec_template.to_json, old.ip, ip)
59
59
  true
60
60
  rescue ::Foreman::Exception => e
61
61
  Foreman::Logging.exception("Unable to kexec", e)
@@ -8,41 +8,36 @@ class Setting::Discovered < ::Setting
8
8
  BLANK_ATTRS << 'discovery_facts_hardware'
9
9
  BLANK_ATTRS << 'discovery_facts_network'
10
10
  BLANK_ATTRS << 'discovery_facts_ipmi'
11
- BLANK_ATTRS << 'discovery_prefix'
12
11
 
13
- def self.load_defaults
14
- # Check the table exists
15
- return unless super
12
+ STRING_PRESENCE_ATTRS = ['discovery_hostname', 'discovery_prefix']
13
+ validates :value, :presence => true, :if => proc { |s| STRING_PRESENCE_ATTRS.include?(s.name) }
16
14
 
17
- Setting.transaction do
18
- [
19
- self.set('discovery_location', N_("The default location to place discovered hosts in"), "", N_("Discovery location"), nil, { :collection => Proc.new {Hash[Location.all.map{|loc| [loc[:title], loc[:title]]}]} }),
20
- self.set('discovery_organization', N_("The default organization to place discovered hosts in"), "", N_("Discovery organization"), nil, { :collection => Proc.new {Hash[Organization.all.map{|org| [org[:title], org[:title]]}]} }),
21
- self.set('discovery_fact', N_("Fact name to use for primary interface detection"), "discovery_bootif", N_("Interface fact")),
22
- self.set('discovery_auto_bond', N_("Automatic bond interface (if another interface is detected on the same VLAN via LLDP)"), false, N_("Create bond interfaces")),
23
- self.set('discovery_clean_facts', N_("Clean all reported facts during provisioning (except discovery facts)"), false, N_("Clean all facts")),
24
- self.set('discovery_hostname', N_("List of facts to use for the hostname (separated by comma, first wins)"), "discovery_bootif", N_("Hostname facts")),
25
- self.set('discovery_auto', N_("Automatically provision newly discovered hosts, according to the provisioning rules"), false, N_("Auto provisioning")),
26
- self.set('discovery_reboot', N_("Automatically reboot or kexec discovered host during provisioning"), true, N_("Reboot")),
27
- self.set('discovery_prefix', N_("The default prefix to use for the host name, must start with a letter"), "mac", N_("Hostname prefix")),
28
- self.set('discovery_fact_column', N_("Extra facter columns to show in host lists (separate by comma)"), "", N_("Fact columns")),
29
- self.set('discovery_facts_highlights', N_("Regex to organize facts for highlights section - e.g. ^(abc|cde)$"), "", N_("Highlighted facts")),
30
- self.set('discovery_facts_storage', N_("Regex to organize facts for storage section"), "", N_("Storage facts")),
31
- self.set('discovery_facts_software', N_("Regex to organize facts for software section"), "", N_("Software facts")),
32
- self.set('discovery_facts_hardware', N_("Regex to organize facts for hardware section"), "", N_("Hardware facts")),
33
- self.set('discovery_facts_network', N_("Regex to organize facts for network section"), "", N_("Network facts")),
34
- self.set('discovery_facts_ipmi', N_("Regex to organize facts for ipmi section"), "", N_("IPMI facts")),
35
- self.set('discovery_lock', N_("Automatically generate PXE configuration to pin a newly discovered host to discovery"), false, N_("Lock PXE")),
36
- self.set('discovery_pxelinux_lock_template', N_("PXELinux template to be used when pinning a host to discovery"), 'pxelinux_discovery', N_("Locked PXELinux template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
37
- self.set('discovery_pxegrub_lock_template', N_("PXEGrub template to be used when pinning a host to discovery"), 'pxegrub_discovery', N_("Locked PXEGrub template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
38
- self.set('discovery_pxegrub2_lock_template', N_("PXEGrub2 template to be used when pinning a host to discovery"), 'pxegrub2_discovery', N_("Locked PXEGrub2 template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
39
- self.set('discovery_always_rebuild_dns', N_("Force DNS entries creation when provisioning discovered host"), true, N_("Force DNS")),
40
- self.set('discovery_error_on_existing', N_("Do not allow to discover existing managed host matching MAC of a provisioning NIC (errors out early)"), false, N_("Error on existing NIC")),
41
- self.set('discovery_naming', N_("Discovery hostname naming pattern"), 'Fact', N_("Type of name generator"), nil, {:collection => Proc.new {::Host::Discovered::NAMING_PATTERNS} }),
42
- ].compact.each { |s| self.create s.update(:category => "Setting::Discovered")}
43
- end
44
-
45
- true
15
+ def self.default_settings
16
+ [
17
+ self.set('discovery_location', N_("The default location to place discovered hosts in"), "", N_("Discovery location"), nil, { :collection => Proc.new {Hash[Location.all.map{|loc| [loc[:title], loc[:title]]}]} }),
18
+ self.set('discovery_organization', N_("The default organization to place discovered hosts in"), "", N_("Discovery organization"), nil, { :collection => Proc.new {Hash[Organization.all.map{|org| [org[:title], org[:title]]}]} }),
19
+ self.set('discovery_fact', N_("Fact name to use for primary interface detection"), "discovery_bootif", N_("Interface fact")),
20
+ self.set('discovery_auto_bond', N_("Automatic bond interface (if another interface is detected on the same VLAN via LLDP)"), false, N_("Create bond interfaces")),
21
+ self.set('discovery_clean_facts', N_("Clean all reported facts during provisioning (except discovery facts)"), false, N_("Clean all facts")),
22
+ self.set('discovery_hostname', N_("List of facts to use for the hostname (separated by comma, first wins)"), "discovery_bootif", N_("Hostname facts")),
23
+ self.set('discovery_auto', N_("Automatically provision newly discovered hosts, according to the provisioning rules"), false, N_("Auto provisioning")),
24
+ self.set('discovery_reboot', N_("Automatically reboot or kexec discovered host during provisioning"), true, N_("Reboot")),
25
+ self.set('discovery_prefix', N_("The default prefix to use for the host name, must start with a letter"), "mac", N_("Hostname prefix")),
26
+ self.set('discovery_fact_column', N_("Extra facter columns to show in host lists (separate by comma)"), "", N_("Fact columns")),
27
+ self.set('discovery_facts_highlights', N_("Regex to organize facts for highlights section - e.g. ^(abc|cde)$"), "", N_("Highlighted facts")),
28
+ self.set('discovery_facts_storage', N_("Regex to organize facts for storage section"), "", N_("Storage facts")),
29
+ self.set('discovery_facts_software', N_("Regex to organize facts for software section"), "", N_("Software facts")),
30
+ self.set('discovery_facts_hardware', N_("Regex to organize facts for hardware section"), "", N_("Hardware facts")),
31
+ self.set('discovery_facts_network', N_("Regex to organize facts for network section"), "", N_("Network facts")),
32
+ self.set('discovery_facts_ipmi', N_("Regex to organize facts for ipmi section"), "", N_("IPMI facts")),
33
+ self.set('discovery_lock', N_("Automatically generate PXE configuration to pin a newly discovered host to discovery"), false, N_("Lock PXE")),
34
+ self.set('discovery_pxelinux_lock_template', N_("PXELinux template to be used when pinning a host to discovery"), 'pxelinux_discovery', N_("Locked PXELinux template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
35
+ self.set('discovery_pxegrub_lock_template', N_("PXEGrub template to be used when pinning a host to discovery"), 'pxegrub_discovery', N_("Locked PXEGrub template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
36
+ self.set('discovery_pxegrub2_lock_template', N_("PXEGrub2 template to be used when pinning a host to discovery"), 'pxegrub2_discovery', N_("Locked PXEGrub2 template name"), nil, { :collection => Proc.new {Hash[ProvisioningTemplate.where(:template_kind => TemplateKind.find_by_name(:snippet)).map{|template| [template[:name], template[:name]]}]} }),
37
+ self.set('discovery_always_rebuild_dns', N_("Force DNS entries creation when provisioning discovered host"), true, N_("Force DNS")),
38
+ self.set('discovery_error_on_existing', N_("Do not allow to discover existing managed host matching MAC of a provisioning NIC (errors out early)"), false, N_("Error on existing NIC")),
39
+ self.set('discovery_naming', N_("Discovery hostname naming pattern"), 'Fact', N_("Type of name generator"), nil, {:collection => Proc.new {::Host::Discovered::NAMING_PATTERNS} }),
40
+ ]
46
41
  end
47
42
 
48
43
  def self.discovery_fact_column_array
@@ -6,7 +6,7 @@ module ForemanDiscovery
6
6
  raise(::Foreman::Exception.new(N_("Discovered host '%{host}' has all NICs filtered out, filter: %{filter}") %
7
7
  {:host => host, :filter => Setting[:ignored_interface_identifiers]})) if interfaces.size == 0
8
8
  bootif_mac = FacterUtils::bootif_mac(facts).try(:downcase)
9
- detected = interfaces.detect { |_, values| values[:macaddress].try(:downcase) == bootif_mac && values[:ipaddress].present? }
9
+ detected = interfaces.detect { |_, values| values[:macaddress].try(:downcase) == bootif_mac && (values[:ipaddress].present? || values[:ipaddress6].present?) }
10
10
  Rails.logger.debug "Discovery fact parser detected primary interface: #{detected}"
11
11
  # return the detected interface as array [name, facts]
12
12
  detected || raise(::Foreman::Exception.new(N_("Unable to find primary NIC with %{mac} specified via '%{fact}', NIC filter: %{filter}") %
@@ -1,7 +1,6 @@
1
1
  class ForemanDiscovery::HostConverter
2
-
3
2
  # Converts discovered host to managed host without uptading the database.
4
- # Record must be saved explicitly (using save! or update_attributes! or similar).
3
+ # Record must be saved explicitly (using save! or update! or similar).
5
4
  # Creates shallow copy.
6
5
  def self.to_managed(original_host, set_managed = true, set_build = true, added_attributes = {})
7
6
  host = original_host.becomes(::Host::Managed)
@@ -35,4 +34,41 @@ class ForemanDiscovery::HostConverter
35
34
  host.build = true
36
35
  end
37
36
 
37
+ def self.unused_ip_for_subnet(subnet, mac, existing_ip)
38
+ # prefer existing reservation to prevent conflicts
39
+ existing_rec = subnet&.dhcp_proxy&.record(subnet.network, mac)
40
+
41
+ if existing_rec && existing_rec.type == "reservation"
42
+ # reuse the reservation
43
+ existing_rec.ip
44
+ else
45
+ # no reservation - find new unused IP
46
+ ipam = subnet.unused_ip(mac)
47
+ raise(::Foreman::Exception.new(N_("IPAM must be configured for subnet '%s'"), subnet)) unless ipam.present?
48
+
49
+ # None IPAM returns nil - in that case keep the current address
50
+ suggested_ip = ipam.suggest_ip
51
+ suggested_ip.nil? ? existing_ip : suggested_ip
52
+ end
53
+ end
54
+
55
+ def self.unused_ip_for_host(host, new_subnet = nil, new_subnet6 = nil)
56
+ host.interfaces.each do |interface|
57
+ next unless interface.managed?
58
+
59
+ interface.subnet = new_subnet if new_subnet
60
+ interface.subnet6 = new_subnet6 if new_subnet6
61
+ interface.ip = unused_ip_for_subnet(interface.subnet, interface.mac, interface.ip) if interface.subnet
62
+ interface.ip6 = unused_ip_for_subnet(interface.subnet6, interface.mac, interface.ip6) if interface.subnet6
63
+ end
64
+ end
65
+
66
+ def self.change_subnet_and_unused_ip(host, hostgroup)
67
+ if host.subnet != hostgroup.subnet || host.subnet6 != hostgroup.subnet6
68
+ Rails.logger.debug "Discovered host subnets #{[host.subnet, host.subnet6]} do not match hostgroup subnets #{[hostgroup.subnet, hostgroup.subnet6]}"
69
+ unused_ip_for_host(host, host.hostgroup.subnet, host.hostgroup.subnet6)
70
+ else
71
+ Rails.logger.debug "Discovered host subnets #{[host.subnet, host.subnet6]} match hostgroup subnets"
72
+ end
73
+ end
38
74
  end
@@ -0,0 +1,10 @@
1
+ class ForemanDiscovery::HostFactImporter < ::HostFactImporter
2
+ def import_facts(facts)
3
+ # Discovered Hosts won't report in via puppet, so we can use that field to
4
+ # record the last time it sent facts...
5
+ host.last_report = Time.now
6
+ # Set the correct facts type for new foreman facts importing code.
7
+ facts[:_type] = :foreman_discovery
8
+ super(facts)
9
+ end
10
+ end
@@ -4,14 +4,15 @@ module ForemanDiscovery
4
4
  class SubnetAndTaxonomy < ImportHook
5
5
  def after_populate
6
6
  primary_ip = host.primary_interface.ip
7
+ primary_ip6 = host.primary_interface.ip6
7
8
 
8
- unless primary_ip
9
+ unless primary_ip || primary_ip6
9
10
  logger.warn "Unable to assign subnet - reboot trigger may not be possible, primary interface is missing IP address"
10
11
  return
11
12
  end
12
13
 
13
14
  # set subnet
14
- set_subnet(primary_ip)
15
+ set_subnets(primary_ip, primary_ip6)
15
16
  # set location and organization
16
17
  set_location
17
18
  set_organization
@@ -19,18 +20,9 @@ module ForemanDiscovery
19
20
 
20
21
  private
21
22
 
22
- def set_subnet(ip)
23
- host.primary_interface.subnet = suggested_subnet(ip)
24
- end
25
-
26
- def suggested_subnet(ip)
27
- subnet = Subnet.subnet_for(ip)
28
- if subnet
29
- logger.info "Detected subnet: #{subnet} with taxonomy #{subnet.organizations.collect(&:name)}/#{subnet.locations.collect(&:name)}"
30
- else
31
- logger.warn "Subnet could not be detected for #{ip}"
32
- end
33
- subnet
23
+ def set_subnets(ip, ip6)
24
+ host.primary_interface.subnet = ForemanDiscovery::SubnetSuggestion.for(ip: ip, kind: 'IPv4') if ip
25
+ host.primary_interface.subnet6 = ForemanDiscovery::SubnetSuggestion.for(ip: ip6, kind: 'IPv6') if ip6
34
26
  end
35
27
 
36
28
  def set_location
@@ -6,6 +6,7 @@ module ForemanDiscovery::NodeAPI
6
6
  @args = args
7
7
  @connect_params = {
8
8
  :headers => { :accept => :json },
9
+ :timeout => 20, # tighter timeout, discovery performs up to two calls per request
9
10
  }
10
11
 
11
12
  if url.match(/^https/i) && Rails.env != "test"
@@ -0,0 +1,26 @@
1
+ module ForemanDiscovery
2
+ class SubnetSuggestion
3
+ attr_accessor :ip, :kind
4
+
5
+ def self.for(ip:, kind:)
6
+ new(ip: ip, kind: kind).()
7
+ end
8
+
9
+ def initialize(ip:, kind:)
10
+ self.ip = ip
11
+ self.kind = kind
12
+ end
13
+
14
+ def call
15
+ return unless ip
16
+
17
+ subnet = Subnet.unscoped.subnet_for(ip)
18
+ if subnet
19
+ Rails.logger.info "Detected #{kind} subnet: #{subnet} with taxonomy #{subnet.organizations.collect(&:name)}/#{subnet.locations.collect(&:name)}"
20
+ else
21
+ Rails.logger.info "#{kind} subnet could not be detected for #{ip}"
22
+ end
23
+ subnet
24
+ end
25
+ end
26
+ end
@@ -12,9 +12,10 @@ environments. The template must generate JSON format with the following items
12
12
  "kernel", "initram", "append" and "extra". The kexec command is composed in
13
13
  the following way:
14
14
 
15
- kexec --force --reset-vga --append=$append --initrd=$initram $extra $kernel
15
+ kexec --force --debug --append=$append --initrd=$initram $extra $kernel
16
16
 
17
17
  Please read kexec(8) man page for more information about semantics.
18
+ Extra options like --reset-vga can be set via "extra" array.
18
19
  -%>
19
20
  <%
20
21
  mac = @host.facts['discovery_bootif']
@@ -28,7 +29,7 @@ Please read kexec(8) man page for more information about semantics.
28
29
  options << @host.facts['append']
29
30
  options << "domain=#{@host.domain}"
30
31
  options << 'console-setup/ask_detect=false console-setup/layout=USA console-setup/variant=USA keyboard-configuration/layoutcode=us localechooser/translation/warn-light=true localechooser/translation/warn-severe=true'
31
- options << "locale=#{@host.params['lang'] || 'en_US'}"
32
+ options << "locale=#{host_param('lang') || 'en_US'}"
32
33
  options << "inst.stage2=#{@host.operatingsystem.medium_uri(@host)}" if @host.operatingsystem.name.match(/Atomic/i)
33
34
  -%>
34
35
  {