foreman_discovery 15.0.2 → 16.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) 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 +1 -0
  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 +39 -17
  8. data/app/models/host/managed_extensions.rb +2 -2
  9. data/app/models/setting/discovered.rb +26 -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/import_hooks/subnet_and_taxonomy.rb +6 -14
  13. data/app/services/foreman_discovery/node_api/node_resource.rb +1 -0
  14. data/app/services/foreman_discovery/subnet_suggestion.rb +26 -0
  15. data/app/views/discovered_hosts/_discovered_host_modal.html.erb +2 -0
  16. data/app/views/foreman_discovery/debian_kexec.erb +1 -1
  17. data/config/routes.rb +2 -0
  18. data/db/migrate/20150512150432_remove_old_discovery_reader_permissions.rb +1 -1
  19. data/db/migrate/20151023144501_regenerate_red_hat_kexec.rb +1 -1
  20. data/db/migrate/20180412124505_add_priority_score_to_discovery_rules.rb +1 -1
  21. data/extra/discover-host +21 -7
  22. data/lib/foreman_discovery/engine.rb +4 -4
  23. data/lib/foreman_discovery/version.rb +1 -1
  24. data/locale/ca/LC_MESSAGES/foreman_discovery.mo +0 -0
  25. data/locale/ca/foreman_discovery.edit.po +122 -93
  26. data/locale/ca/foreman_discovery.po +28 -8
  27. data/locale/de/LC_MESSAGES/foreman_discovery.mo +0 -0
  28. data/locale/de/foreman_discovery.edit.po +125 -96
  29. data/locale/de/foreman_discovery.po +31 -11
  30. data/locale/en/LC_MESSAGES/foreman_discovery.mo +0 -0
  31. data/locale/en/foreman_discovery.edit.po +118 -88
  32. data/locale/en/foreman_discovery.po +24 -4
  33. data/locale/en_GB/LC_MESSAGES/foreman_discovery.mo +0 -0
  34. data/locale/en_GB/foreman_discovery.edit.po +125 -96
  35. data/locale/en_GB/foreman_discovery.po +31 -11
  36. data/locale/es/LC_MESSAGES/foreman_discovery.mo +0 -0
  37. data/locale/es/foreman_discovery.edit.po +124 -95
  38. data/locale/es/foreman_discovery.po +30 -10
  39. data/locale/foreman_discovery.pot +110 -84
  40. data/locale/fr/LC_MESSAGES/foreman_discovery.mo +0 -0
  41. data/locale/fr/foreman_discovery.edit.po +122 -93
  42. data/locale/fr/foreman_discovery.po +28 -8
  43. data/locale/gl/LC_MESSAGES/foreman_discovery.mo +0 -0
  44. data/locale/gl/foreman_discovery.edit.po +120 -91
  45. data/locale/gl/foreman_discovery.po +26 -6
  46. data/locale/it/LC_MESSAGES/foreman_discovery.mo +0 -0
  47. data/locale/it/foreman_discovery.edit.po +120 -91
  48. data/locale/it/foreman_discovery.po +26 -6
  49. data/locale/ja/LC_MESSAGES/foreman_discovery.mo +0 -0
  50. data/locale/ja/foreman_discovery.edit.po +123 -94
  51. data/locale/ja/foreman_discovery.po +29 -9
  52. data/locale/ko/LC_MESSAGES/foreman_discovery.mo +0 -0
  53. data/locale/ko/foreman_discovery.edit.po +122 -93
  54. data/locale/ko/foreman_discovery.po +28 -8
  55. data/locale/pt_BR/LC_MESSAGES/foreman_discovery.mo +0 -0
  56. data/locale/pt_BR/foreman_discovery.edit.po +123 -94
  57. data/locale/pt_BR/foreman_discovery.po +29 -9
  58. data/locale/ru/LC_MESSAGES/foreman_discovery.mo +0 -0
  59. data/locale/ru/foreman_discovery.edit.po +123 -94
  60. data/locale/ru/foreman_discovery.po +29 -9
  61. data/locale/sv_SE/LC_MESSAGES/foreman_discovery.mo +0 -0
  62. data/locale/sv_SE/foreman_discovery.edit.po +121 -92
  63. data/locale/sv_SE/foreman_discovery.po +27 -7
  64. data/locale/zh_CN/LC_MESSAGES/foreman_discovery.mo +0 -0
  65. data/locale/zh_CN/foreman_discovery.edit.po +122 -93
  66. data/locale/zh_CN/foreman_discovery.po +28 -8
  67. data/locale/zh_TW/LC_MESSAGES/foreman_discovery.mo +0 -0
  68. data/locale/zh_TW/foreman_discovery.edit.po +120 -91
  69. data/locale/zh_TW/foreman_discovery.po +26 -6
  70. data/test/facts/bond0-eth0-eth1-active-passive.json +128 -0
  71. data/test/facts/facts_with_lldp_bond_candidate.json +2 -9
  72. data/test/functional/api/v2/discovered_hosts_controller_test.rb +1 -0
  73. data/test/functional/discovered_hosts_controller_test.rb +15 -6
  74. data/test/integration/discovered_hosts_test.rb +6 -11
  75. data/test/test_helper_discovery.rb +12 -0
  76. data/test/unit/discovered_extensions_test.rb +54 -0
  77. data/test/unit/discovery_attribute_set_test.rb +13 -10
  78. data/test/unit/discovery_rule_test.rb +1 -0
  79. data/test/unit/host_discovered_test.rb +32 -13
  80. data/test/unit/managed_extensions_test.rb +2 -0
  81. metadata +34 -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: ef6a4731f46b5f34f372a87f8da770e0510504664852ef61253677bb587c8842
4
+ data.tar.gz: 75715ed1dae928289c3189443f8f290c6fa95a681678610c0aca980b18825aa1
5
5
  SHA512:
6
- metadata.gz: 6b8adac21ff97553128c433c222a45ee8529a1d83ab75c9a0657d7b536320bc0b7420af6562eecdf5c0f1d684b118d64331c8e96158553edf9f5be93d4b6b600
7
- data.tar.gz: 8ee273cf286df977a582413f006ecc7529b47b4507b2be598cde0c9db6fa91f0b6fbc3fcbfd5ff815873622fe5d1b6d3162cd0ba766c029d2635162a4168889a
6
+ metadata.gz: 5882e04be01b4d3f213acf4b751a0a4784436cd97558ab1e9448f8976e89f86db047c986923da5d62f72757c86430790590c942f0579ba62224f7ce458ef9c9d
7
+ data.tar.gz: 154d39a5d013b431ce6367d5a0f87e06521df092c36a4d95da0114062f4aef569ddd80600ac077db7639b9628739056e66d619de7604e026e45f57406a069c3c
@@ -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