foreman_discovery 4.0.0 → 4.1.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/discovered_hosts_controller.rb +12 -3
  3. data/app/models/host/discovered.rb +28 -25
  4. data/app/models/host/managed_extensions.rb +24 -3
  5. data/app/services/foreman_discovery/host_converter.rb +16 -2
  6. data/app/services/foreman_discovery/node_api/node_resource.rb +117 -0
  7. data/app/services/foreman_discovery/node_api/power.rb +57 -0
  8. data/{lib → app/services}/foreman_discovery/proxy_operations.rb +0 -0
  9. data/app/views/foreman_discovery/redhat_kexec.erb +44 -0
  10. data/db/seeds.d/50_discovery_templates.rb +18 -0
  11. data/lib/foreman_discovery/engine.rb +1 -1
  12. data/lib/foreman_discovery/version.rb +1 -1
  13. data/locale/de/foreman_discovery.po +146 -64
  14. data/locale/en_GB/foreman_discovery.po +146 -64
  15. data/locale/es/foreman_discovery.po +146 -64
  16. data/locale/foreman_discovery.pot +97 -41
  17. data/locale/fr/foreman_discovery.po +146 -64
  18. data/locale/gl/foreman_discovery.po +100 -59
  19. data/locale/it/foreman_discovery.po +106 -59
  20. data/locale/ja/foreman_discovery.po +132 -61
  21. data/locale/ko/foreman_discovery.po +104 -58
  22. data/locale/pt_BR/foreman_discovery.po +106 -59
  23. data/locale/ru/foreman_discovery.po +152 -66
  24. data/locale/sv_SE/foreman_discovery.po +103 -59
  25. data/locale/zh_CN/foreman_discovery.po +104 -58
  26. data/locale/zh_TW/foreman_discovery.po +104 -58
  27. data/test/functional/api/v2/discovered_hosts_controller_test.rb +30 -11
  28. data/test/functional/discovered_hosts_controller_test.rb +30 -26
  29. data/test/unit/discovered_extensions_test.rb +8 -8
  30. metadata +10 -34
  31. data/app/views/api/v2/discovered_hosts/auto_provision.json.rabl +0 -3
  32. data/app/views/api/v2/discovered_hosts/auto_provision_all.json.rabl +0 -3
  33. data/locale/Makefile +0 -62
  34. data/locale/de/LC_MESSAGES/foreman_discovery.mo +0 -0
  35. data/locale/de/foreman_discovery.pox +0 -40
  36. data/locale/en_GB/LC_MESSAGES/foreman_discovery.mo +0 -0
  37. data/locale/en_GB/foreman_discovery.pox +0 -0
  38. data/locale/es/LC_MESSAGES/foreman_discovery.mo +0 -0
  39. data/locale/es/foreman_discovery.pox +0 -41
  40. data/locale/fr/LC_MESSAGES/foreman_discovery.mo +0 -0
  41. data/locale/fr/foreman_discovery.pox +0 -69
  42. data/locale/gl/LC_MESSAGES/foreman_discovery.mo +0 -0
  43. data/locale/gl/foreman_discovery.pox +0 -21
  44. data/locale/it/LC_MESSAGES/foreman_discovery.mo +0 -0
  45. data/locale/it/foreman_discovery.pox +0 -0
  46. data/locale/ja/LC_MESSAGES/foreman_discovery.mo +0 -0
  47. data/locale/ja/foreman_discovery.pox +0 -29
  48. data/locale/ko/LC_MESSAGES/foreman_discovery.mo +0 -0
  49. data/locale/ko/foreman_discovery.pox +0 -189
  50. data/locale/messages.mo +0 -0
  51. data/locale/pt_BR/LC_MESSAGES/foreman_discovery.mo +0 -0
  52. data/locale/pt_BR/foreman_discovery.pox +0 -0
  53. data/locale/ru/LC_MESSAGES/foreman_discovery.mo +0 -0
  54. data/locale/ru/foreman_discovery.pox +0 -0
  55. data/locale/sv_SE/LC_MESSAGES/foreman_discovery.mo +0 -0
  56. data/locale/zanata.xml +0 -28
  57. data/locale/zh_CN/LC_MESSAGES/foreman_discovery.mo +0 -0
  58. data/locale/zh_CN/foreman_discovery.pox +0 -33
  59. data/locale/zh_TW/LC_MESSAGES/foreman_discovery.mo +0 -0
  60. data/locale/zh_TW/foreman_discovery.pox +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa7dff0a0f4756c41176a4e6b6be41f2815375cc
4
- data.tar.gz: 7f83b17405564440f5939d66e15527363d07ad27
3
+ metadata.gz: 4ec0d128aaf7c37440c990fce555943c73bed976
4
+ data.tar.gz: e9b3caa61e5e0b380b14e294927a2d06c097b383
5
5
  SHA512:
6
- metadata.gz: a588c3c7c19787aa5fa1cb36557e786889f294c0005bd4f1cb12dd614be77b88b2a8dbf3f46483ecf5129ad4cabc8f2a9a8cfd025eda077c9f160e0dc10debd0
7
- data.tar.gz: 0e0e298f9a57c4a310e474f7dd3c6dd38b226d407a55a4a02a8e5c687b06c7149fd5c65da5cd951a61aa01e79833b7dd06539ad522a7e15f3d010aa268e1349d
6
+ metadata.gz: cf30e4317aab32579aca2cfa16e788917c7fee2300f717a50ca0c5915936431e4e0d3f852f3bde6423bfd4b6fe2761eb48958a1317ab2f14d84e7d97daf51336
7
+ data.tar.gz: 8929a780f304b8d9f56df0e10d076593271d0c593fc0d575fd4ccb7f384d5594565ed6fcea2a914f7362c73f884b1dcdb03bf481444fabc883b9c985443ae892
@@ -121,8 +121,13 @@ module Api
121
121
  def auto_provision
122
122
  Host.transaction do
123
123
  if rule = find_discovery_rule(@discovered_host)
124
- msg = _("Host %{host} was provisioned with rule %{rule}") % {:host => @discovered_host.name, :rule => rule.name}
125
- process_response perform_auto_provision(@discovered_host, rule), msg
124
+ if perform_auto_provision(@discovered_host, rule)
125
+ msg = _("Host %{host} was provisioned with rule %{rule}") % {:host => @discovered_host.name, :rule => rule.name}
126
+ render :json => {:message => msg}
127
+ else
128
+ msg = _("Unable to provision %{host}: %{errors}") % {:host => @discovered_host.name, :errors => @discovered_host.errors.full_messages.join(' ')}
129
+ render :json => {:message => msg}, :status => :unprocessable_entity
130
+ end
126
131
  else
127
132
  render_error :custom_error, :status => :not_found,
128
133
  :locals => {
@@ -145,6 +150,7 @@ module Api
145
150
  result = false
146
151
  end
147
152
 
153
+ total_count = 0
148
154
  Host.transaction do
149
155
  overall_errors = ""
150
156
  Host::Discovered.all.each do |discovered_host|
@@ -154,6 +160,8 @@ module Api
154
160
  errors = discovered_host.errors.full_messages.join(' ')
155
161
  logger.warn "Failed to auto provision host %s: %s" % [discovered_host.name, errors]
156
162
  overall_errors << "#{discovered_host.name}: #{errors} "
163
+ else
164
+ total_count += 1
157
165
  end
158
166
  else
159
167
  logger.warn "No rule found for host %s" % discovered_host.name
@@ -161,7 +169,8 @@ module Api
161
169
  end
162
170
 
163
171
  if result
164
- process_success _("Discovered hosts are provisioning now")
172
+ msg = _("%s discovered hosts were provisioned") % total_count
173
+ render :json => {:message => msg}
165
174
  else
166
175
  render_error :custom_error,
167
176
  :status => :unprocessable_entity,
@@ -1,5 +1,3 @@
1
- require 'foreman_discovery/proxy_operations'
2
-
3
1
  class Host::Discovered < ::Host::Base
4
2
  include ScopedSearchExtensions
5
3
 
@@ -125,39 +123,44 @@ class Host::Discovered < ::Host::Base
125
123
  read_attribute(:root_pass).blank? ? (hostgroup.try(:root_pass) || Setting[:root_pass]) : read_attribute(:root_pass)
126
124
  end
127
125
 
126
+ def proxied?
127
+ subnet.present? && subnet.discovery.present?
128
+ end
129
+
128
130
  def proxy_url
129
- if subnet.present? && subnet.discovery.present?
130
- { :url => subnet.discovery.url + "/discovery/#{self.ip}", :type => 'proxy'}
131
- else
132
- { :url => "http://#{self.ip}:8443", :type => 'foreman' }
133
- end
131
+ proxied? ? subnet.discovery.url + "/discovery/#{self.ip}" : "https://#{self.ip}:8443"
134
132
  end
135
133
 
136
134
  def refresh_facts
137
- # TODO: Can we rely on self.ip? The lease might expire/change....
138
- begin
139
- logger.debug "retrieving facts from proxy from url: #{proxy_url[:url]}"
140
- facts = ForemanDiscovery::ProxyOperations.new(:url => proxy_url[:url], :operation => 'facts').parse_get_operation
141
- rescue Exception => e
142
- raise _("Could not get facts from proxy %{url}: %{error}") % {:url => proxy_url[:url], :error => e}
143
- end
144
-
145
- return self.class.import_host_and_facts facts
135
+ # TODO implement new-style service, get rid of old client (legacy_api? cannot be used!)
136
+ facts = ForemanDiscovery::ProxyOperations.new(:url => proxy_url, :operation => 'facts').parse_get_operation
137
+ self.class.import_host_and_facts facts
138
+ rescue Exception => e
139
+ raise _("Could not get facts from proxy %{url}: %{error}") % {:url => proxy_url, :error => e}
146
140
  end
147
141
 
148
- def reboot
149
- logger.info "ForemanDiscovery: Rebooting #{name}"
150
- proxy_url = self.proxy_url
151
-
152
- if proxy_url[:type] == 'proxy'
153
- ForemanDiscovery::ProxyOperations.new(:url => proxy_url[:url], :operation => 'reboot').
154
- parse_put_operation.try(:fetch, 'result')
142
+ def reboot legacy_api = ForemanDiscovery::HostConverter.legacy_host(self)
143
+ if legacy_api
144
+ if proxied?
145
+ resource = ::ForemanDiscovery::NodeAPI::Power.legacy_proxied_service(:url => proxy_url)
146
+ else
147
+ resource = ::ForemanDiscovery::NodeAPI::Power.legacy_direct_service(:url => proxy_url)
148
+ end
155
149
  else
156
- ::ProxyAPI::BMC.new(:url => "http://#{self.ip}:8443").power :action => "cycle"
150
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => proxy_url)
157
151
  end
152
+ resource.reboot
158
153
  rescue => e
159
154
  ::Foreman::Logging.exception("Unable to reboot #{name}", e)
160
- raise ::Foreman::WrappedException.new(e, N_("Unable to reboot %{name}: %{msg}"), :name => name, :msg => e.to_s)
155
+ raise ::Foreman::WrappedException.new(e, N_("Unable to reboot %{name} via %{url}: %{msg}"), :name => name, :url => proxy_url, :msg => e.to_s)
156
+ end
157
+
158
+ def kexec json
159
+ resource = ::ForemanDiscovery::NodeAPI::Power.service(:url => proxy_url)
160
+ resource.kexec json
161
+ rescue => e
162
+ ::Foreman::Logging.exception("Unable to perform kexec on #{name}", e)
163
+ raise ::Foreman::WrappedException.new(e, N_("Unable to perform kexec on %{name} via %{url}: %{msg}"), :name => name, :url => proxy_url, :msg => e.to_s)
161
164
  end
162
165
 
163
166
  def self.model_name
@@ -7,6 +7,9 @@ module Host::ManagedExtensions
7
7
  after_save :delete_discovery_attribute_set
8
8
 
9
9
  belongs_to :discovery_rule
10
+
11
+ # extra flag for post_queue callbacks which has no access to facts
12
+ attr_accessor :legacy_api
10
13
  end
11
14
 
12
15
  def queue_reboot
@@ -14,12 +17,15 @@ module Host::ManagedExtensions
14
17
  return if new_record? # Discovered Hosts already exist, and new_records will break `find`
15
18
  return unless type_changed? and ::Host::Base.find(self.id).type == "Host::Discovered"
16
19
  # reboot task must be high priority and there is no compensation action apparently
17
- post_queue.create(:name => _("Rebooting %s") % self, :priority => 10000,
18
- :action => [self, :setReboot])
20
+ if facts['discovery_kexec'] && provisioning_template(:kind => 'kexec')
21
+ post_queue.create(:name => _("Reloading kernel on %s") % self, :priority => 10000, :action => [self, :setKexec])
22
+ else
23
+ post_queue.create(:name => _("Rebooting %s") % self, :priority => 10000, :action => [self, :setReboot])
24
+ end
19
25
  end
20
26
 
21
27
  def setReboot
22
- old.becomes(Host::Discovered).reboot
28
+ old.becomes(Host::Discovered).reboot legacy_api
23
29
  # It is too late to report error in the post_queue, we catch them and
24
30
  # continue. If flash is implemented for new hosts (http://projects.theforeman.org/issues/10559)
25
31
  # we can report the error to the user perhaps.
@@ -28,6 +34,21 @@ module Host::ManagedExtensions
28
34
  true
29
35
  end
30
36
 
37
+ def boot_url pxe_file
38
+ operatingsystem.medium_vars_to_uri("#{medium.path}/#{operatingsystem.url_for_boot(pxe_file)}", architecture.name, operatingsystem).to_s
39
+ end
40
+
41
+ def setKexec
42
+ template = provisioning_template(:kind => 'kexec')
43
+ @kernel = boot_url(:kernel)
44
+ @initrd = boot_url(:initrd)
45
+ json = unattended_render(template)
46
+ old.becomes(Host::Discovered).kexec json
47
+ true
48
+ rescue ::Foreman::Exception
49
+ true
50
+ end
51
+
31
52
  def delete_discovery_attribute_set
32
53
  return if new_record?
33
54
  DiscoveryAttributeSet.destroy_all(:host_id => self.id) if type_changed?
@@ -14,9 +14,23 @@ class ForemanDiscovery::HostConverter
14
14
  host.managed = set_managed
15
15
  host.primary_interface.managed = set_managed
16
16
  end
17
- # set build only and only on final save (otherwise interfaces are not being identified)
18
- host.build = set_build if set_build
17
+ # set build only and only on final save (facts are deleted)
18
+ if set_build
19
+ # set legacy_api flag for post_queue actions
20
+ host.legacy_api = self.legacy_host(host)
21
+ # do not delete all facts (keep discovery ones)
22
+ host.define_singleton_method(:clearFacts) do
23
+ keep_ids = FactValue.where("host_id = #{host.id}").joins(:fact_name).where("fact_names.name like 'discovery_%'").pluck(:id)
24
+ FactValue.where("host_id = #{host.id} and id not in (?)", keep_ids).delete_all
25
+ end
26
+ # set build flag (also deletes facts)
27
+ host.build = set_build
28
+ end
19
29
  host
20
30
  end
21
31
 
32
+ def self.legacy_host(host)
33
+ Gem::Version.new(host.facts['discovery_version'] || '1.0.0') < Gem::Version.new('3.0.0')
34
+ end
35
+
22
36
  end
@@ -0,0 +1,117 @@
1
+ module ForemanDiscovery::NodeAPI
2
+ class NodeResource
3
+ def initialize(args)
4
+ raise ArgumentError, "Options must be hash" unless args.is_a?(Hash)
5
+ raise ArgumentError, "Option 'url' must be provided" unless args[:url]
6
+ @args = args
7
+ @connect_params = {
8
+ :headers => { :accept => :json },
9
+ }
10
+
11
+ if url.match(/^https/i) && Rails.env != "test"
12
+ if is_proxy?
13
+ logger.debug "Connecting to proxy, setting up SSL client cert"
14
+ cert = Setting[:ssl_certificate]
15
+ ca_cert = Setting[:ssl_ca_file]
16
+ hostprivkey = Setting[:ssl_priv_key]
17
+
18
+ @connect_params.merge!(
19
+ :ssl_client_cert => OpenSSL::X509::Certificate.new(File.read(cert)),
20
+ :ssl_client_key => OpenSSL::PKey::RSA.new(File.read(hostprivkey)),
21
+ :ssl_ca_file => ca_cert,
22
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER
23
+ )
24
+ else
25
+ logger.debug "Connecting without proxy, not using SSL client cert"
26
+ @connect_params.merge!(
27
+ :verify_ssl => OpenSSL::SSL::VERIFY_NONE
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ def url
34
+ @args[:url]
35
+ end
36
+
37
+ def scheme
38
+ URI.parse(url).scheme
39
+ rescue Exception => e
40
+ raise ArgumentError, "Option 'url' must be valid URI: #{e}"
41
+ end
42
+
43
+ def port
44
+ URI.parse(url).port
45
+ rescue Exception => e
46
+ raise ArgumentError, "Option 'url' must be valid URI: #{e}"
47
+ end
48
+
49
+ def root_path
50
+ URI.parse(url).path
51
+ rescue Exception => e
52
+ raise ArgumentError, "Option 'url' must be valid URI: #{e}"
53
+ end
54
+
55
+ def is_proxy?
56
+ root_path.starts_with?('/discovery')
57
+ end
58
+
59
+ protected
60
+
61
+ attr_reader :connect_params
62
+
63
+ def resource
64
+ @resource ||= RestClient::Resource.new(url, connect_params)
65
+ end
66
+
67
+ def logger; Rails.logger; end
68
+
69
+ private
70
+
71
+ def parse(response)
72
+ if response and response.code >= 200 and response.code < 300
73
+ return response.body.present? ? JSON.parse(response.body) : {}
74
+ else
75
+ raise ::Foreman::Exception.new(N_("Image API returned HTTP/%{code} with '%{body}"), :body => response.body, :code => response.code)
76
+ end
77
+ rescue => e
78
+ raise ::Foreman::WrappedException.new(e, N_("Image API processing error: %{msg} (HTTP/%{code}, body: %{body})"), :msg => e.to_s, :body => response.try(:body), :code => response.try(:code))
79
+ end
80
+
81
+ def get(payload = {}, path = nil)
82
+ logger.debug("Image API GET #{url} (path=#{path}, payload=#{payload})")
83
+ if path
84
+ resource[URI.escape(path)].get payload
85
+ else
86
+ resource.get payload
87
+ end
88
+ end
89
+
90
+ def post(payload, path = nil)
91
+ logger.debug("Image API POST #{url} (path=#{path}, payload=#{payload})")
92
+ if path
93
+ resource[path].post payload
94
+ else
95
+ resource.post payload
96
+ end
97
+ end
98
+
99
+ def put(payload, path = nil)
100
+ logger.debug("Image API PUT #{url} (path=#{path}, payload=#{payload})")
101
+ if path
102
+ resource[path].put payload
103
+ else
104
+ resource.put payload
105
+ end
106
+ end
107
+
108
+ def delete(path)
109
+ logger.debug("Image API DELETE #{url} (path=#{path})")
110
+ if path
111
+ resource[path].delete
112
+ else
113
+ resource.delete
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,57 @@
1
+ module ForemanDiscovery::NodeAPI
2
+ class Power
3
+ class << self
4
+ def service(data)
5
+ PowerService.new(data)
6
+ end
7
+
8
+ def legacy_direct_service(data)
9
+ PowerLegacyDirectService.new(data)
10
+ end
11
+
12
+ def legacy_proxied_service(data)
13
+ PowerLegacyProxiedService.new(data)
14
+ end
15
+ end
16
+ end
17
+
18
+ class PowerService < NodeResource
19
+ def url
20
+ @args[:url] + "/power"
21
+ end
22
+
23
+ def reboot
24
+ put({}, "/reboot")
25
+ end
26
+
27
+ def kexec(json)
28
+ put(json, "/kexec")
29
+ end
30
+
31
+ end
32
+
33
+ # legacy /reboot call direct
34
+ class PowerLegacyDirectService < NodeResource
35
+ def initialize(args)
36
+ super args
37
+ raise ArgumentError, "Legacy direct service only supports http scheme" if scheme != 'http'
38
+ raise ArgumentError, "Legacy direct service only supports port 8443" if port != 8443
39
+ Foreman::Deprecation.deprecation_warning("1.11", "Discovery legacy API will be removed, use the new Power API")
40
+ end
41
+
42
+ def reboot
43
+ ::ProxyAPI::BMC.new(:url => url).power :action => "cycle"
44
+ end
45
+ end
46
+
47
+ # legacy /reboot call via proxy
48
+ class PowerLegacyProxiedService < NodeResource
49
+ def initialize(args)
50
+ super args.merge(:proxy => true)
51
+ end
52
+
53
+ def reboot
54
+ put({}, "/reboot")
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,44 @@
1
+ <%#
2
+ kind: kexec
3
+ name: Red Hat kexec
4
+ oses:
5
+ - CentOS 4
6
+ - CentOS 5
7
+ - CentOS 6
8
+ - CentOS 7
9
+ - Fedora 19
10
+ - Fedora 20
11
+ - Fedora 21
12
+ - Fedora 22
13
+ - RedHat 4
14
+ - RedHat 5
15
+ - RedHat 6
16
+ - RedHat 7
17
+ -%>
18
+ <%#
19
+ This template is used to pass command line options to kexec when reloading
20
+ kernel on a discovered host instead of rebooting. This is useful in PXE-less
21
+ environments. The template must generate JSON format with three items:
22
+ "kernel", "initram" and "append". Read kexec(8) man page for more
23
+ information about semantics.
24
+ -%>
25
+ <%
26
+ mac = @host.facts['discovery_bootif']
27
+ bootif = '00-' + mac.gsub(':', '-')
28
+ ip_cidr = @host.facts['discovery_ip_cidr']
29
+ ip = @host.facts['discovery_ip']
30
+ mask = @host.facts['discovery_netmask']
31
+ gw = @host.facts['discovery_gateway']
32
+ dns = @host.facts['discovery_dns']
33
+ append = @host.facts['append']
34
+ -%>
35
+ {
36
+ "kernel": "<%= @kernel %>",
37
+ "initram": "<%= @initrd %>",
38
+ <% if (@host.operatingsystem.name == 'Fedora' and @host.operatingsystem.major.to_i > 16) or
39
+ (@host.operatingsystem.name != 'Fedora' and @host.operatingsystem.major.to_i >= 7) -%>
40
+ "append": "ks=<%= foreman_url('provision') + "&static=yes" %> inst.ks.sendmac <%= "ip=#{ip}::#{gw}:#{mask}:::none nameserver=#{dns} ksdevice=bootif BOOTIF=#{bootif} #{append}" %>"
41
+ <% else -%>
42
+ "append": "ks=<%= foreman_url('provision') + "&static=yes" %> kssendmac <%= "ip=#{ip} netmask=#{mask} gateway=#{gw} dns=#{dns} ksdevice=#{mac} BOOTIF=#{bootif} #{append}" %>"
43
+ <% end -%>
44
+ }
@@ -0,0 +1,18 @@
1
+ kind = TemplateKind.find_or_create_by_name('kexec')
2
+
3
+ ProvisioningTemplate.without_auditing do
4
+ content = File.read(File.join(ForemanDiscovery::Engine.root, 'app', 'views', 'foreman_discovery', 'redhat_kexec.erb'))
5
+ tmpl = ProvisioningTemplate.find_or_create_by_name(
6
+ :name => 'Discovery Red Hat kexec',
7
+ :template_kind_id => kind.id,
8
+ :snippet => false,
9
+ :template => content
10
+ )
11
+ tmpl.attributes = {
12
+ :template => content,
13
+ :default => true,
14
+ :vendor => "Foreman Discovery",
15
+ :locked => false
16
+ }
17
+ tmpl.save!(:validate => false) if tmpl.changes.present?
18
+ end