foreman_discovery 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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