foreman_discovery 15.0.2 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  {