foreman_discovery 15.1.0 → 16.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/discovered_hosts_controller.rb +10 -3
  3. data/app/controllers/api/v2/discovery_rules_controller.rb +1 -1
  4. data/app/controllers/concerns/foreman/controller/discovered_extensions.rb +4 -0
  5. data/app/controllers/discovered_hosts_controller.rb +1 -0
  6. data/app/controllers/discovery_rules_controller.rb +1 -1
  7. data/app/models/discovery_rule.rb +1 -1
  8. data/app/models/host/discovered.rb +39 -17
  9. data/app/models/host/managed_extensions.rb +2 -2
  10. data/app/models/setting/discovered.rb +26 -33
  11. data/app/services/foreman_discovery/fact_parser.rb +1 -1
  12. data/app/services/foreman_discovery/host_converter.rb +38 -2
  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/discovered_hosts/_discovered_host_modal.html.erb +2 -0
  17. data/app/views/foreman_discovery/debian_kexec.erb +1 -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 +21 -7
  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 +226 -263
  27. data/locale/ca/foreman_discovery.po +28 -8
  28. data/locale/de/LC_MESSAGES/foreman_discovery.mo +0 -0
  29. data/locale/de/foreman_discovery.edit.po +229 -265
  30. data/locale/de/foreman_discovery.po +31 -11
  31. data/locale/en/LC_MESSAGES/foreman_discovery.mo +0 -0
  32. data/locale/en/foreman_discovery.edit.po +169 -119
  33. data/locale/en/foreman_discovery.po +24 -4
  34. data/locale/en_GB/LC_MESSAGES/foreman_discovery.mo +0 -0
  35. data/locale/en_GB/foreman_discovery.edit.po +237 -266
  36. data/locale/en_GB/foreman_discovery.po +31 -11
  37. data/locale/es/LC_MESSAGES/foreman_discovery.mo +0 -0
  38. data/locale/es/foreman_discovery.edit.po +236 -265
  39. data/locale/es/foreman_discovery.po +30 -10
  40. data/locale/foreman_discovery.pot +110 -84
  41. data/locale/fr/LC_MESSAGES/foreman_discovery.mo +0 -0
  42. data/locale/fr/foreman_discovery.edit.po +230 -262
  43. data/locale/fr/foreman_discovery.po +28 -8
  44. data/locale/gl/LC_MESSAGES/foreman_discovery.mo +0 -0
  45. data/locale/gl/foreman_discovery.edit.po +221 -261
  46. data/locale/gl/foreman_discovery.po +26 -6
  47. data/locale/it/LC_MESSAGES/foreman_discovery.mo +0 -0
  48. data/locale/it/foreman_discovery.edit.po +221 -261
  49. data/locale/it/foreman_discovery.po +26 -6
  50. data/locale/ja/LC_MESSAGES/foreman_discovery.mo +0 -0
  51. data/locale/ja/foreman_discovery.edit.po +235 -264
  52. data/locale/ja/foreman_discovery.po +29 -9
  53. data/locale/ko/LC_MESSAGES/foreman_discovery.mo +0 -0
  54. data/locale/ko/foreman_discovery.edit.po +223 -262
  55. data/locale/ko/foreman_discovery.po +28 -8
  56. data/locale/pt_BR/LC_MESSAGES/foreman_discovery.mo +0 -0
  57. data/locale/pt_BR/foreman_discovery.edit.po +233 -264
  58. data/locale/pt_BR/foreman_discovery.po +29 -9
  59. data/locale/ru/LC_MESSAGES/foreman_discovery.mo +0 -0
  60. data/locale/ru/foreman_discovery.edit.po +227 -265
  61. data/locale/ru/foreman_discovery.po +29 -9
  62. data/locale/sv_SE/LC_MESSAGES/foreman_discovery.mo +0 -0
  63. data/locale/sv_SE/foreman_discovery.edit.po +222 -262
  64. data/locale/sv_SE/foreman_discovery.po +27 -7
  65. data/locale/zh_CN/LC_MESSAGES/foreman_discovery.mo +0 -0
  66. data/locale/zh_CN/foreman_discovery.edit.po +234 -263
  67. data/locale/zh_CN/foreman_discovery.po +28 -8
  68. data/locale/zh_TW/LC_MESSAGES/foreman_discovery.mo +0 -0
  69. data/locale/zh_TW/foreman_discovery.edit.po +223 -261
  70. data/locale/zh_TW/foreman_discovery.po +26 -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/functional/api/v2/discovered_hosts_controller_test.rb +1 -0
  74. data/test/functional/discovered_hosts_controller_test.rb +15 -6
  75. data/test/integration/discovered_hosts_test.rb +1 -0
  76. data/test/test_helper_discovery.rb +12 -0
  77. data/test/unit/discovered_extensions_test.rb +54 -0
  78. data/test/unit/discovery_attribute_set_test.rb +1 -0
  79. data/test/unit/discovery_rule_test.rb +1 -0
  80. data/test/unit/host_discovered_test.rb +32 -13
  81. data/test/unit/managed_extensions_test.rb +2 -0
  82. metadata +36 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 279682fd72f6cdf11166d4feb018e13f7fbe17137a23186507178000a387aeb0
4
- data.tar.gz: 2d2fb544411f4db3594f0cd46ddc2a89afe216a12fdbdff769fc39e63e78879d
3
+ metadata.gz: 6ac7d31fa470fc53b81672be0f21309d8df92eef200481143055c9ad3af467be
4
+ data.tar.gz: a71a4512c1bd4f90d0163dfaf07b5a670112f4825395a004c723e2f6da98d8ba
5
5
  SHA512:
6
- metadata.gz: 5ffd16f5840e43beed5ae06306292569d445027b4a0a48a547cf16e9e3f8e8eba9ccb94796cbe20d723b478c02e5fa0a0b2667f1fa8417b1a0520575cd8f121a
7
- data.tar.gz: 1e7defe44767c8dc6d2716d7a5eec5a30b57ef24008cf4a10ec3e6e88bc766b8fcd7a147d44d9ccd032a5ace60f981e6fd9502217760b30746c336b9bf84486f
6
+ metadata.gz: e271f6b3990930e40277c8921254e1fd45ae89796b7d4693816d6083eb5fdcce078eb310b73579bab68e7c07c51c3e3ebf2bf93242339e6b62424d6bc48692df
7
+ data.tar.gz: ae858e8f800237891cb92ce4868d43820e9f20e409c26f0bd887df6bef3c783c9e3dffbb90ce8b91971a3b5b331334593c0968712a93dc3c5c7df174346651f3
@@ -102,9 +102,16 @@ module Api
102
102
  state = true
103
103
  User.as_anonymous_admin do
104
104
  @discovered_host = Host::Discovered.import_host(facts)
105
- Rails.logger.warn 'Discovered facts import unsuccessful, skipping auto provisioning' unless @discovered_host
106
- if Setting['discovery_auto'] && @discovered_host && (rule = find_discovery_rule(@discovered_host))
107
- state = perform_auto_provision(@discovered_host, rule)
105
+ # Host::Based.set_taxonomies sets taxonomies during import from either
106
+ # facts or via Global Foreman setting "default_taxonomy", reset it back so
107
+ # the anonymous admin can see all records. We set taxonomy explicitly via
108
+ # discovery SubnetAndTaxonomy import hook and enforce taxnomy manually
109
+ # in find_discovery_rule method via validate_rule_by_taxonomy.
110
+ Taxonomy.no_taxonomy_scope do
111
+ Rails.logger.warn 'Discovered facts import unsuccessful, skipping auto provisioning' unless @discovered_host
112
+ if Setting['discovery_auto'] && @discovered_host && (rule = find_discovery_rule(@discovered_host))
113
+ state = perform_auto_provision(@discovered_host, rule)
114
+ end
108
115
  end
109
116
  end
110
117
  process_response state
@@ -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
@@ -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
@@ -137,12 +137,12 @@ class Host::Discovered < ::Host::Base
137
137
  subnet.present? && subnet.discovery.present?
138
138
  end
139
139
 
140
- def proxy_url
141
- proxied? ? subnet.discovery.url + "/discovery/#{self.ip}" : "https://#{self.ip}:8443"
140
+ def proxy_url(node_ip)
141
+ proxied? ? subnet.discovery.url + "/discovery/#{node_ip}" : "https://#{node_ip}:8443"
142
142
  end
143
143
 
144
144
  def refresh_facts
145
- facts = ::ForemanDiscovery::NodeAPI::Inventory.new(:url => proxy_url).facter
145
+ facts = ::ForemanDiscovery::NodeAPI::Inventory.new(:url => proxy_url(self.ip)).facter
146
146
  self.class.import_host facts
147
147
  import_facts facts
148
148
  rescue => e
@@ -150,20 +150,42 @@ class Host::Discovered < ::Host::Base
150
150
  raise ::Foreman::WrappedException.new(e, N_("Could not get facts from proxy %{url}: %{error}"), :url => proxy_url, :error => e)
151
151
  end
152
152
 
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)
153
+ def reboot(old_ip = nil, new_ip = nil)
154
+ # perform the action against the original lease as well as the new reservation
155
+ ips = [old_ip, new_ip, self.ip].compact.uniq
156
+ logger.debug "Performing reboot calls against #{ips.to_sentence}, facts left #{facts.count}"
157
+ ips.each do |next_ip|
158
+ begin
159
+ node_url = proxy_url(next_ip)
160
+ logger.debug "Performing reboot call against #{node_url}"
161
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => node_url)
162
+ return true if resource.reboot
163
+ rescue => e
164
+ msg = N_("Unable to perform reboot on %{name} (%{url}): %{msg}")
165
+ ::Foreman::Logging.exception(msg % { :name => name, :url => node_url, :msg => e.to_s }, e)
166
+ end
167
+ end
168
+ msg = N_("Unable to perform %{action} on %{ips}")
169
+ raise ::Foreman::Exception.new(msg, action: "reboot", ips: ips.to_sentence)
170
+ end
171
+
172
+ def kexec(json, old_ip = nil, new_ip = nil)
173
+ # perform the action against the original lease as well as the new reservation
174
+ ips = [old_ip, new_ip, self.ip].compact.uniq
175
+ logger.debug "Performing kexec calls against #{ips.to_sentence}, #{facts.count} facts left"
176
+ ips.each do |next_ip|
177
+ begin
178
+ node_url = proxy_url(next_ip)
179
+ logger.debug "Performing kexec call against #{node_url}"
180
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => node_url)
181
+ return true if resource.kexec(json)
182
+ rescue => e
183
+ msg = N_("Unable to perform kexec on %{name} (%{url}): %{msg}")
184
+ ::Foreman::Logging.exception(msg % { :name => name, :url => node_url, :msg => e.to_s }, e)
185
+ end
186
+ end
187
+ msg = N_("Unable to perform %{action} on %{ips}")
188
+ raise ::Foreman::Exception.new(msg, action: "kexec", ips: ips.to_sentence)
167
189
  end
168
190
 
169
191
  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)
@@ -10,39 +10,32 @@ class Setting::Discovered < ::Setting
10
10
  BLANK_ATTRS << 'discovery_facts_ipmi'
11
11
  BLANK_ATTRS << 'discovery_prefix'
12
12
 
13
- def self.load_defaults
14
- # Check the table exists
15
- return unless super
16
-
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
13
+ def self.default_settings
14
+ [
15
+ 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]]}]} }),
16
+ 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]]}]} }),
17
+ self.set('discovery_fact', N_("Fact name to use for primary interface detection"), "discovery_bootif", N_("Interface fact")),
18
+ 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")),
19
+ self.set('discovery_clean_facts', N_("Clean all reported facts during provisioning (except discovery facts)"), false, N_("Clean all facts")),
20
+ self.set('discovery_hostname', N_("List of facts to use for the hostname (separated by comma, first wins)"), "discovery_bootif", N_("Hostname facts")),
21
+ self.set('discovery_auto', N_("Automatically provision newly discovered hosts, according to the provisioning rules"), false, N_("Auto provisioning")),
22
+ self.set('discovery_reboot', N_("Automatically reboot or kexec discovered host during provisioning"), true, N_("Reboot")),
23
+ self.set('discovery_prefix', N_("The default prefix to use for the host name, must start with a letter"), "mac", N_("Hostname prefix")),
24
+ self.set('discovery_fact_column', N_("Extra facter columns to show in host lists (separate by comma)"), "", N_("Fact columns")),
25
+ self.set('discovery_facts_highlights', N_("Regex to organize facts for highlights section - e.g. ^(abc|cde)$"), "", N_("Highlighted facts")),
26
+ self.set('discovery_facts_storage', N_("Regex to organize facts for storage section"), "", N_("Storage facts")),
27
+ self.set('discovery_facts_software', N_("Regex to organize facts for software section"), "", N_("Software facts")),
28
+ self.set('discovery_facts_hardware', N_("Regex to organize facts for hardware section"), "", N_("Hardware facts")),
29
+ self.set('discovery_facts_network', N_("Regex to organize facts for network section"), "", N_("Network facts")),
30
+ self.set('discovery_facts_ipmi', N_("Regex to organize facts for ipmi section"), "", N_("IPMI facts")),
31
+ self.set('discovery_lock', N_("Automatically generate PXE configuration to pin a newly discovered host to discovery"), false, N_("Lock PXE")),
32
+ 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]]}]} }),
33
+ 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]]}]} }),
34
+ 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]]}]} }),
35
+ self.set('discovery_always_rebuild_dns', N_("Force DNS entries creation when provisioning discovered host"), true, N_("Force DNS")),
36
+ 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")),
37
+ self.set('discovery_naming', N_("Discovery hostname naming pattern"), 'Fact', N_("Type of name generator"), nil, {:collection => Proc.new {::Host::Discovered::NAMING_PATTERNS} }),
38
+ ]
46
39
  end
47
40
 
48
41
  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
@@ -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
@@ -17,6 +17,8 @@
17
17
  <% if show_location_tab? %>
18
18
  <%= select_f f, :location_id, Location.my_locations, :id, :to_label, {}, {:size => 'col-md-10'} %>
19
19
  <% end %>
20
+
21
+ <div>Changing the Host's Subnet is not possible via Customize Host form, use Create Host or Auto provisioning and set the desired Subnet in the Hostgroup.</div>
20
22
  </div>
21
23
  <div class="modal-footer">
22
24
  <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
@@ -28,7 +28,7 @@ Please read kexec(8) man page for more information about semantics.
28
28
  options << @host.facts['append']
29
29
  options << "domain=#{@host.domain}"
30
30
  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'}"
31
+ options << "locale=#{host_param('lang') || 'en_US'}"
32
32
  options << "inst.stage2=#{@host.operatingsystem.medium_uri(@host)}" if @host.operatingsystem.name.match(/Atomic/i)
33
33
  -%>
34
34
  {
@@ -6,6 +6,8 @@ Rails.application.routes.draw do
6
6
  post 'os_selected_discovered_hosts' => 'hosts#os_selected'
7
7
  post 'medium_selected_discovered_hosts' => 'hosts#medium_selected'
8
8
 
9
+ get 'discovered_hosts/help', :action => :welcome, :controller => 'discovered_hosts'
10
+
9
11
  constraints(:id => /[^\/]+/) do
10
12
  resources :discovered_hosts, :except => [:new, :create] do
11
13
  member do
@@ -1,7 +1,7 @@
1
1
  class RemoveOldDiscoveryReaderPermissions < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  Permission.where("name like '%_discovery_rules' and resource_type is null").each do |permission|
4
- permission.update_attributes(:resource_type => 'DiscoveryRule')
4
+ permission.update(:resource_type => 'DiscoveryRule')
5
5
  end
6
6
  end
7
7
 
@@ -1,7 +1,7 @@
1
1
  class RegenerateRedHatKexec < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  t = ProvisioningTemplate.find_by_name("Discovery Red Hat kexec")
4
- t.update_attributes(:template => t.template.sub(/rescue ''$/, 'if mac')) if t
4
+ t.update(:template => t.template.sub(/rescue ''$/, 'if mac')) if t
5
5
  end
6
6
 
7
7
  def down
@@ -9,7 +9,7 @@
9
9
  # and makes sure there are no two entities with the same priority.
10
10
  DiscoveryRules.reset_column_information
11
11
  DiscoveryRules.unscoped.reorder(:priority, :created_at).find_each do |rule|
12
- rule.update_attributes!(:priority => score)
12
+ rule.update!(:priority => score)
13
13
  score += delta
14
14
  end
15
15
  change_column :discovery_rules, :priority, :integer, null: false
@@ -3,10 +3,12 @@ require 'optparse'
3
3
  require 'json'
4
4
  require 'rest-client'
5
5
 
6
- def find_base name="default"
7
- return name if File.exists?(name)
6
+ def find_base name = "default"
7
+ return name if File.exist?(name)
8
+
8
9
  file = File.absolute_path(File.dirname(__FILE__) + "../../test/facts/#{name}.json")
9
- raise "Unable to find file #{file}" unless File.exists?(file)
10
+ raise "Unable to find file #{file}" unless File.exist?(file)
11
+
10
12
  file
11
13
  end
12
14
 
@@ -14,6 +16,7 @@ base = find_base
14
16
  version = "3.4.0"
15
17
  interfaces = []
16
18
  preserve_interfaces = false
19
+ use_facts_endpoint = false
17
20
  primary = nil
18
21
  bootif = nil
19
22
  url = "http://admin:changeme@localhost:3000"
@@ -38,6 +41,10 @@ OptionParser.new do |opts|
38
41
  preserve_interfaces = true
39
42
  end
40
43
 
44
+ opts.on("-a", "--facts", "Use host facts endpoint") do |v|
45
+ use_facts_endpoint = true
46
+ end
47
+
41
48
  opts.on("-pNAME", "--primary=NAME", "Interface to use as primary (defaults to the first one)") do |v|
42
49
  primary = v
43
50
  end
@@ -70,8 +77,8 @@ unless preserve_interfaces
70
77
  json["interfaces"] = interfaces.map{|i| i.first}.join(',')
71
78
  interfaces.each do |iface|
72
79
  name, subnet, ipo, mac = iface
73
- mac = (["52"] + 5.times.map { '%02x' % rand(0..255) }).join(':') unless mac
74
- ipo = rand(1..253) unless ipo
80
+ mac ||= (["52"] + 5.times.map { '%02x' % rand(0..255) }).join(':')
81
+ ipo ||= rand(1..253)
75
82
  ip = "192.168.#{subnet}.#{ipo}"
76
83
  json["macaddress_#{name}"] = mac
77
84
  json["ipaddress_#{name}"] = ip
@@ -83,7 +90,14 @@ unless preserve_interfaces
83
90
  end
84
91
  end
85
92
  puts JSON.pretty_generate(json)
86
- resource = RestClient::Resource.new(url + "/api/v2/discovered_hosts/facts", verify_ssl: OpenSSL::SSL::VERIFY_NONE)
87
- response = resource.post({"facts" => json}.to_json, {content_type: :json, accept: :json})
93
+ if use_facts_endpoint
94
+ endpoint = url + "/api/v2/hosts/facts"
95
+ payload = {"facts" => json, "name" => json["hostname"] || json["networking"]["fqdn"]}
96
+ else
97
+ endpoint = url + "/api/v2/discovered_hosts/facts"
98
+ payload = {"facts" => json}
99
+ end
100
+ resource = RestClient::Resource.new(endpoint, verify_ssl: OpenSSL::SSL::VERIFY_NONE)
101
+ response = resource.post(payload.to_json, {content_type: :json, accept: :json})
88
102
  puts response.code
89
103
  puts response.body