foreman_ovirt 0.3.0 → 2.0.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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/{LICENSE.txt → LICENSE} +0 -2
  3. data/README.md +140 -0
  4. data/Rakefile +37 -43
  5. data/app/assets/javascripts/foreman_ovirt/display.js +10 -0
  6. data/app/assets/javascripts/foreman_ovirt/host_edit.js +61 -0
  7. data/app/assets/javascripts/foreman_ovirt/nic_info.js +3 -0
  8. data/app/assets/javascripts/foreman_ovirt/ovirt.js +261 -0
  9. data/app/controllers/concerns/foreman_ovirt/compute_resources_vms_controller.rb +29 -0
  10. data/app/controllers/concerns/foreman_ovirt/parameters_extension.rb +35 -0
  11. data/app/controllers/foreman_ovirt/concerns/compute_resources_controller_extensions.rb +60 -0
  12. data/app/helpers/ovirt_compute_resource_helper.rb +26 -0
  13. data/app/models/concerns/fog_extensions/ovirt/server.rb +42 -0
  14. data/app/models/concerns/fog_extensions/ovirt/template.rb +14 -0
  15. data/app/models/concerns/fog_extensions/ovirt/volume.rb +11 -0
  16. data/app/models/foreman_ovirt/ovirt.rb +752 -0
  17. data/app/views/api/v2/compute_resources/ovirt.json.rabl +1 -0
  18. data/app/views/compute_resources/form/_ovirt.html.erb +14 -0
  19. data/app/views/compute_resources/show/_ovirt.html.erb +16 -0
  20. data/app/views/compute_resources_vms/form/ovirt/_base.html.erb +83 -0
  21. data/app/views/compute_resources_vms/form/ovirt/_network.html.erb +32 -0
  22. data/app/views/compute_resources_vms/form/ovirt/_volume.html.erb +16 -0
  23. data/app/views/compute_resources_vms/index/_ovirt.html.erb +12 -0
  24. data/app/views/compute_resources_vms/index/_ovirt_json.erb +6 -0
  25. data/app/views/compute_resources_vms/show/_ovirt.html.erb +55 -0
  26. data/app/views/images/form/_ovirt.html.erb +4 -0
  27. data/config/routes.rb +8 -13
  28. data/db/migrate/20250810212811_update_legacy_ovirt_compute_resource_type.rb +21 -0
  29. data/lib/foreman_ovirt/engine.rb +70 -0
  30. data/lib/foreman_ovirt/version.rb +3 -0
  31. data/lib/foreman_ovirt.rb +2 -1
  32. data/lib/tasks/foreman_ovirt_tasks.rake +30 -0
  33. data/locale/Makefile +73 -0
  34. data/locale/en/foreman_ovirt.po +19 -0
  35. data/locale/foreman_ovirt.pot +19 -0
  36. data/locale/gemspec.rb +2 -0
  37. data/package.json +39 -0
  38. data/test/factories/foreman_ovirt_factories.rb +5 -0
  39. data/test/test_plugin_helper.rb +6 -0
  40. data/test/unit/foreman_ovirt_test.rb +11 -0
  41. data/webpack/components/extensions/HostDetails/DetailsTabCards/OvirtCard.js +132 -0
  42. data/webpack/components/ovirt.js +20 -0
  43. data/webpack/global_index.js +14 -0
  44. data/webpack/global_test_setup.js +11 -0
  45. data/webpack/index.js +1 -0
  46. data/webpack/test_setup.js +17 -0
  47. metadata +157 -128
  48. data/.document +0 -5
  49. data/Gemfile +0 -14
  50. data/Gemfile.lock +0 -33
  51. data/README.rdoc +0 -20
  52. data/VERSION +0 -1
  53. data/app/controllers/foreman_ovirt/auth_source_ovirts_controller.rb +0 -57
  54. data/app/controllers/foreman_ovirt/dashboard_controller.rb +0 -10
  55. data/app/controllers/foreman_ovirt/hosts_controller.rb +0 -17
  56. data/app/models/foreman_ovirt/auth_source_ovirt.rb +0 -78
  57. data/app/models/foreman_ovirt/user_extensions.rb +0 -26
  58. data/app/views/foreman_ovirt/auth_source_ovirts/_form.html.erb +0 -17
  59. data/app/views/foreman_ovirt/auth_source_ovirts/edit.html.erb +0 -3
  60. data/app/views/foreman_ovirt/auth_source_ovirts/index.html.erb +0 -22
  61. data/app/views/foreman_ovirt/auth_source_ovirts/new.html.erb +0 -3
  62. data/app/views/foreman_ovirt/auth_source_ovirts/welcome.html.erb +0 -8
  63. data/app/views/foreman_ovirt/hosts/_overview.html.erb +0 -27
  64. data/app/views/foreman_ovirt/hosts/show.html.erb +0 -48
  65. data/app/views/foreman_ovirt/hosts/show_graphs.html.erb +0 -13
  66. data/app/views/layouts/application_ovirt.html.erb +0 -36
  67. data/config/initializers/ovirt_setup.rb +0 -30
  68. data/foreman_ovirt.gemspec +0 -74
  69. data/lib/engine.rb +0 -9
@@ -0,0 +1,752 @@
1
+ require 'foreman/exception'
2
+ require 'uri'
3
+
4
+ module ForemanOvirt
5
+ class Ovirt < ComputeResource
6
+ ALLOWED_DISPLAY_TYPES = %w(vnc spice)
7
+
8
+ validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true,
9
+ :url_schema => ['http', 'https']
10
+ validates :display_type, :inclusion => { :in => ALLOWED_DISPLAY_TYPES }
11
+ validates :keyboard_layout, :inclusion => { :in => ALLOWED_KEYBOARD_LAYOUTS }
12
+ validates :user, :password, :presence => true
13
+ after_validation :connect, :update_available_operating_systems unless Rails.env.test?
14
+ before_save :validate_quota
15
+
16
+ alias_attribute :datacenter, :uuid
17
+
18
+ delegate :clusters, :quotas, :templates, :instance_types, :to => :client
19
+
20
+ def self.available?
21
+ Fog::Compute.providers.include?(:ovirt)
22
+ end
23
+
24
+ def self.model_name
25
+ ComputeResource.model_name
26
+ end
27
+
28
+ def user_data_supported?
29
+ true
30
+ end
31
+
32
+ def host_compute_attrs(host)
33
+ super.tap do |attrs|
34
+ attrs[:os] = { :type => determine_os_type(host) } if supports_operating_systems?
35
+ end
36
+ end
37
+
38
+ def capabilities
39
+ [:build, :image, :new_volume]
40
+ end
41
+
42
+ def find_vm_by_uuid(uuid)
43
+ vm = super
44
+ return unless vm
45
+
46
+ vm.define_singleton_method(:vm_template) do
47
+ self.template
48
+ end
49
+
50
+ vm
51
+ rescue Fog::Ovirt::Errors::OvirtEngineError
52
+ raise(ActiveRecord::RecordNotFound)
53
+ end
54
+
55
+ def supports_update?
56
+ true
57
+ end
58
+
59
+ def supports_operating_systems?
60
+ if client.respond_to?(:operating_systems)
61
+ unless attrs.key?(:available_operating_systems)
62
+ update_available_operating_systems
63
+ save
64
+ end
65
+ attrs[:available_operating_systems] != :unsupported
66
+ else
67
+ false
68
+ end
69
+ rescue Foreman::FingerprintException
70
+ logger.info "Unable to verify OS capabilities, SSL certificate verification failed"
71
+ false
72
+ end
73
+
74
+ def determine_os_type(host)
75
+ return nil unless host
76
+ return host.params['ovirt_ostype'] if host.params['ovirt_ostype']
77
+ ret = "other_linux"
78
+ return ret unless host.operatingsystem
79
+ os_name = os_name_mapping(host)
80
+ arch_name = arch_name_mapping(host)
81
+
82
+ available = available_operating_systems.select { |os| os[:name].present? }
83
+ match_found = false
84
+ best_matches = available.sort_by do |os|
85
+ rating = 0.0
86
+ if os[:name].include?(os_name)
87
+ match_found = true
88
+ rating += 100
89
+ # prefer the shorter names a bit in case we have not found more important some specifics
90
+ rating += (1.0 / os[:name].length)
91
+ # bonus for major or major_minor
92
+ rating += 10 if os[:name].include?("#{os_name}_#{host.operatingsystem.major}")
93
+ rating += 5 if os[:name].include?("#{os_name}_#{host.operatingsystem.major}_#{host.operatingsystem.minor}")
94
+ # bonus for architecture
95
+ rating += 10 if arch_name && os[:name].include?(arch_name)
96
+ end
97
+ rating
98
+ end
99
+
100
+ unless match_found
101
+ logger.debug { "No oVirt OS type found, returning other OS" }
102
+ return available.first[:name]
103
+ end
104
+
105
+ logger.debug { "Available oVirt OS types: #{best_matches.map { |x| x[:name] }.join(',')}" }
106
+ best_matches.last[:name] if best_matches.last
107
+ end
108
+
109
+ def available_operating_systems
110
+ if attrs.key?(:available_operating_systems)
111
+ attrs[:available_operating_systems]
112
+ else
113
+ raise Foreman::Exception.new("Listing operating systems is not supported by the current version")
114
+ end
115
+ end
116
+
117
+ def provided_attributes
118
+ super.merge({:mac => :mac})
119
+ end
120
+
121
+ def ovirt_quota=(ovirt_quota_id)
122
+ attrs[:ovirt_quota_id] = ovirt_quota_id
123
+ end
124
+
125
+ def ovirt_quota
126
+ attrs[:ovirt_quota_id].presence
127
+ end
128
+
129
+ def available_images
130
+ templates
131
+ end
132
+
133
+ def image_exists?(image)
134
+ client.templates.get(image).present?
135
+ rescue => e
136
+ Foreman::Logging.exception("Error while checking if image exists", e)
137
+ false
138
+ end
139
+
140
+ def template(id)
141
+ compute = client.templates.get(id) || raise(ActiveRecord::RecordNotFound)
142
+ compute.interfaces
143
+ compute.volumes
144
+ compute
145
+ end
146
+
147
+ def instance_type(id)
148
+ client.instance_types.get(id) || raise(ActiveRecord::RecordNotFound)
149
+ end
150
+
151
+ def display_types
152
+ ALLOWED_DISPLAY_TYPES
153
+ end
154
+
155
+ # Check if HTTPS is mandatory, since rest_client will fail with a POST
156
+ def test_https_required
157
+ RestClient.post url, {} if URI(url).scheme == 'http'
158
+ true
159
+ rescue => e
160
+ case e.message
161
+ when /406/
162
+ true
163
+ else
164
+ raise e
165
+ end
166
+ end
167
+ private :test_https_required
168
+
169
+ def test_connection(options = {})
170
+ super
171
+ connect(options)
172
+ end
173
+
174
+ def connect(options = {})
175
+ return unless connection_properties_valid?
176
+
177
+ update_public_key options
178
+ datacenters && test_https_required
179
+ rescue => e
180
+ case e.message
181
+ when /404/
182
+ errors.add(:url, e.message)
183
+ when /302/
184
+ errors.add(:url, _('HTTPS URL is required for API access'))
185
+ when /401/
186
+ errors.add(:user, e.message)
187
+ else
188
+ errors.add(:base, e.message)
189
+ end
190
+ end
191
+
192
+ def connection_properties_valid?
193
+ errors[:url].empty? && errors[:username].empty? && errors[:password].empty?
194
+ end
195
+
196
+ def datacenters(options = {})
197
+ client.datacenters(options).map { |dc| [dc[:name], dc[:id]] }
198
+ end
199
+
200
+ def get_datacenter_uuid(datacenter)
201
+ return @datacenter_uuid if @datacenter_uuid
202
+ if Foreman.is_uuid?(datacenter)
203
+ @datacenter_uuid = datacenter
204
+ else
205
+ @datacenter_uuid = datacenters.select { |dc| dc[0] == datacenter }
206
+ raise ::Foreman::Exception.new(N_('Datacenter was not found')) if @datacenter_uuid.empty?
207
+ @datacenter_uuid = @datacenter_uuid.first[1]
208
+ end
209
+ @datacenter_uuid
210
+ end
211
+
212
+ def editable_network_interfaces?
213
+ # we can't decide whether the networks are available when we
214
+ # don't know the cluster_id, assuming it's possible
215
+ true
216
+ end
217
+
218
+ def vnic_profiles
219
+ client.list_vnic_profiles
220
+ end
221
+
222
+ def networks(opts = {})
223
+ if opts[:cluster_id]
224
+ client.clusters.get(opts[:cluster_id]).networks
225
+ else
226
+ []
227
+ end
228
+ end
229
+
230
+ def available_clusters
231
+ clusters
232
+ end
233
+
234
+ def available_networks(cluster_id = nil)
235
+ raise ::Foreman::Exception.new(N_('Cluster ID is required to list available networks')) if cluster_id.nil?
236
+ networks({:cluster_id => cluster_id})
237
+ end
238
+
239
+ def available_storage_domains(cluster_id = nil)
240
+ storage_domains
241
+ end
242
+
243
+ def storage_domains(opts = {})
244
+ client.storage_domains({:role => ['data', 'volume']}.merge(opts))
245
+ end
246
+
247
+ def start_vm(uuid)
248
+ vm = find_vm_by_uuid(uuid)
249
+ if vm.comment.to_s =~ %r{cloud-config|^#!/}
250
+ vm.start_with_cloudinit(:blocking => true, :user_data => vm.comment, :use_custom_script => true)
251
+ vm.comment = ''
252
+ vm.save
253
+ else
254
+ vm.start(:blocking => true)
255
+ end
256
+ end
257
+
258
+ def start_with_cloudinit(uuid, user_data = nil)
259
+ find_vm_by_uuid(uuid).start_with_cloudinit(:blocking => true, :user_data => user_data, :use_custom_script => true)
260
+ end
261
+
262
+ def sanitize_inherited_vm_attributes(args, template, instance_type)
263
+ # Override memory an cores values if template and/or instance type is/are provided.
264
+ # Take template values if blank values for VM attributes, because oVirt will fail if empty values are present in VM definition
265
+ # Instance type values always take precedence on templates or vm provided values
266
+ if template
267
+ template_cores = template.cores.to_i if template.cores.present?
268
+ template_memory = template.memory.to_i if template.memory.present?
269
+ args[:cores] = template_cores if template_cores && args[:cores].blank?
270
+ args[:memory] = template_memory if template_memory && args[:memory].blank?
271
+ end
272
+ if instance_type
273
+ instance_type_cores = instance_type.cores.to_i if instance_type.cores.present?
274
+ instance_type_sockets = instance_type.sockets.to_i if instance_type.sockets.present?
275
+ instance_type_memory = instance_type.memory.to_i if instance_type.memory.present?
276
+ args[:cores] = instance_type_cores if instance_type_cores
277
+ args[:sockets] = instance_type_sockets if instance_type_sockets
278
+ args[:memory] = instance_type_memory if instance_type_memory
279
+ end
280
+ end
281
+
282
+ def create_vm(args = {})
283
+ args[:comment] = args[:user_data] if args[:user_data]
284
+ args[:template] = args[:image_id] if args[:image_id]
285
+ template = template(args[:template]) if args[:template]
286
+ instance_type = instance_type(args[:instance_type]) unless args[:instance_type].empty?
287
+
288
+ args[:cluster] = get_ovirt_id(clusters, 'cluster', args[:cluster])
289
+
290
+ args[:template] = args.delete(:vm_template) if args.key?(:vm_template)
291
+
292
+ sanitize_inherited_vm_attributes(args, template, instance_type)
293
+ preallocate_and_clone_disks(args, template) if args[:volumes_attributes].present? && template.present?
294
+
295
+ vm = super({ :first_boot_dev => 'network', :quota => ovirt_quota }.merge(args))
296
+
297
+ begin
298
+ create_interfaces(vm, args[:interfaces_attributes], args[:cluster]) unless args[:interfaces_attributes].empty?
299
+ create_volumes(vm, args[:volumes_attributes]) unless args[:volumes_attributes].empty?
300
+ rescue => e
301
+ destroy_vm vm.id
302
+ raise e
303
+ end
304
+ vm
305
+ end
306
+
307
+ def get_ovirt_id(argument_list, argument_key, argument_value)
308
+ return argument_value if argument_value.blank?
309
+ if argument_list.none? { |a| a.name == argument_value || a.id == argument_value }
310
+ raise Foreman::Exception.new("The #{argument_key} #{argument_value} is not valid, enter a correct id or name")
311
+ else
312
+ argument_list.detect { |a| a.name == argument_value }.try(:id) || argument_value
313
+ end
314
+ end
315
+
316
+ def preallocate_and_clone_disks(args, template)
317
+ volumes_to_change = args[:volumes_attributes].values.select { |x| x[:id].present? }
318
+ return unless volumes_to_change.present?
319
+
320
+ template_disks = template.volumes
321
+
322
+ disks = volumes_to_change.map do |volume|
323
+ if volume[:preallocate] == '1'
324
+ {:id => volume[:id], :sparse => 'false', :format => 'raw', :storage_domain => volume[:storage_domain]}
325
+ else
326
+ template_volume = template_disks.detect { |v| v.id == volume["id"] }
327
+ {:id => volume["id"], :storage_domain => volume["storage_domain"]} if template_volume.storage_domain != volume["storage_domain"]
328
+ end
329
+ end.compact
330
+
331
+ args.merge!(:clone => true, :disks => disks) if disks.present?
332
+ end
333
+
334
+ def vm_instance_defaults
335
+ super.merge(
336
+ :memory => 1024.megabytes,
337
+ :cores => '1',
338
+ :sockets => '1',
339
+ :display => { :type => display_type,
340
+ :keyboard_layout => keyboard_layout,
341
+ :port => -1,
342
+ :monitors => 1 }
343
+ )
344
+ end
345
+
346
+ def new_vm(attr = {})
347
+ vm = super
348
+
349
+ vm.define_singleton_method(:vm_template) do
350
+ self.template
351
+ end
352
+
353
+ vm.define_singleton_method(:vm_template=) do |value|
354
+ self.template = value
355
+ end
356
+
357
+ interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
358
+ interfaces.map { |i| vm.interfaces << new_interface(i) }
359
+ volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
360
+ volumes.map { |v| vm.volumes << new_volume(v) }
361
+ vm
362
+ end
363
+
364
+ def new_interface(attr = {})
365
+ Fog::Ovirt::Compute::Interface.new(attr)
366
+ end
367
+
368
+ def new_volume(attr = {})
369
+ set_preallocated_attributes!(attr, attr[:preallocate])
370
+ raise ::Foreman::Exception.new(N_('VM volume attributes are not set properly')) unless attr.all? { |key, value| value.is_a? String }
371
+ Fog::Ovirt::Compute::Volume.new(attr)
372
+ end
373
+
374
+ def save_vm(uuid, attr)
375
+ vm = find_vm_by_uuid(uuid)
376
+ vm.attributes.deep_merge!(attr.deep_symbolize_keys).deep_symbolize_keys
377
+ update_interfaces(vm, attr[:interfaces_attributes])
378
+ update_volumes(vm, attr[:volumes_attributes])
379
+ vm.interfaces
380
+ vm.volumes
381
+ vm.save
382
+ end
383
+
384
+ def destroy_vm(uuid)
385
+ find_vm_by_uuid(uuid).destroy
386
+ rescue ActiveRecord::RecordNotFound
387
+ true
388
+ end
389
+
390
+ def supports_vms_pagination?
391
+ true
392
+ end
393
+
394
+ def parse_vms_list_params(params)
395
+ max = (params['length'] || 10).to_i
396
+ {
397
+ :search => params['search']['value'] || '',
398
+ :max => max,
399
+ :page => (params['start'].to_i / max) + 1,
400
+ :without_details => true,
401
+ }
402
+ end
403
+
404
+ def console(uuid)
405
+ vm = find_vm_by_uuid(uuid)
406
+ raise "VM is not running!" if vm.status == "down"
407
+ opts = if vm.display[:secure_port]
408
+ { :host_port => vm.display[:secure_port], :ssl_target => true }
409
+ else
410
+ { :host_port => vm.display[:port] }
411
+ end
412
+ WsProxy.start(opts.merge(:host => vm.display[:address], :password => vm.ticket)).merge(:name => vm.name, :type => vm.display[:type])
413
+ end
414
+
415
+ def update_required?(old_attrs, new_attrs)
416
+ return true if super(old_attrs, new_attrs)
417
+
418
+ new_attrs[:interfaces_attributes]&.each do |key, interface|
419
+ return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' # ignore the template
420
+ end
421
+
422
+ new_attrs[:volumes_attributes]&.each do |key, volume|
423
+ return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' # ignore the template
424
+ end
425
+
426
+ false
427
+ end
428
+
429
+ def associated_host(vm)
430
+ associate_by("mac", vm.interfaces.map(&:mac))
431
+ end
432
+
433
+ def self.provider_friendly_name
434
+ "oVirt"
435
+ end
436
+
437
+ def display_type
438
+ attrs[:display].presence || 'vnc'
439
+ end
440
+
441
+ def display_type=(display)
442
+ attrs[:display] = display.downcase
443
+ end
444
+
445
+ def keyboard_layout
446
+ attrs[:keyboard_layout].presence || 'en-us'
447
+ end
448
+
449
+ def keyboard_layout=(layout)
450
+ attrs[:keyboard_layout] = layout.downcase
451
+ end
452
+
453
+ def public_key
454
+ attrs[:public_key]
455
+ end
456
+
457
+ def public_key=(key)
458
+ attrs[:public_key] = key
459
+ end
460
+
461
+ def normalize_vm_attrs(vm_attrs)
462
+ normalized = slice_vm_attributes(vm_attrs, ['cores', 'interfaces_attributes', 'memory'])
463
+ normalized['cluster_id'] = get_ovirt_id(clusters, 'cluster', vm_attrs['cluster'])
464
+ normalized['cluster_name'] = clusters.detect { |c| c.id == normalized['cluster_id'] }.try(:name)
465
+
466
+ normalized['template_id'] = get_ovirt_id(templates, 'template', vm_attrs['template'])
467
+ normalized['template_name'] = templates.detect { |t| t.id == normalized['template_id'] }.try(:name)
468
+
469
+ cluster_networks = networks(:cluster_id => normalized['cluster_id'])
470
+
471
+ interface_attrs = vm_attrs['interfaces_attributes'] || {}
472
+ normalized['interfaces_attributes'] = interface_attrs.inject({}) do |interfaces, (key, nic)|
473
+ interfaces.update(key => { 'name' => nic['name'],
474
+ 'network_id' => nic['network'],
475
+ 'network_name' => cluster_networks.detect { |n| n.id == nic['network'] }.try(:name),
476
+ })
477
+ end
478
+
479
+ volume_attrs = vm_attrs['volumes_attributes'] || {}
480
+ normalized['volumes_attributes'] = volume_attrs.inject({}) do |volumes, (key, vol)|
481
+ volumes.update(key => { 'size' => memory_gb_to_bytes(vol['size_gb']).to_s,
482
+ 'storage_domain_id' => vol['storage_domain'],
483
+ 'storage_domain_name' => storage_domains.detect { |d| d.id == vol['storage_domain'] }.try(:name),
484
+ 'preallocate' => to_bool(vol['preallocate']),
485
+ 'bootable' => to_bool(vol['bootable']),
486
+ })
487
+ end
488
+
489
+ normalized
490
+ end
491
+
492
+ def nictypes
493
+ [
494
+ OpenStruct.new({:id => 'virtio', :name => 'VirtIO'}),
495
+ OpenStruct.new({:id => 'rtl8139', :name => 'rtl8139'}),
496
+ OpenStruct.new({:id => 'e1000', :name => 'e1000'}),
497
+ OpenStruct.new({:id => 'pci_passthrough', :name => 'PCI Passthrough'}),
498
+ ]
499
+ end
500
+
501
+ def validate_quota
502
+ if attrs[:ovirt_quota_id].nil?
503
+ attrs[:ovirt_quota_id] = client.quotas.first.id
504
+ else
505
+ attrs[:ovirt_quota_id] = get_ovirt_id(client.quotas, 'quota', attrs[:ovirt_quota_id])
506
+ end
507
+ end
508
+
509
+ protected
510
+
511
+ def bootstrap(args)
512
+ client.servers.bootstrap vm_instance_defaults.merge(args.to_h)
513
+ rescue Fog::Errors::Error => e
514
+ Foreman::Logging.exception("Failed to bootstrap vm", e)
515
+ errors.add(:base, e.to_s)
516
+ false
517
+ end
518
+
519
+ def client
520
+ return @client if @client
521
+ client = ::Fog::Compute.new(
522
+ :provider => "ovirt",
523
+ :ovirt_username => user,
524
+ :ovirt_password => password,
525
+ :ovirt_url => url,
526
+ :ovirt_datacenter => uuid,
527
+ :ovirt_ca_cert_store => ca_cert_store(public_key),
528
+ :public_key => public_key,
529
+ :api_version => 'v4'
530
+ )
531
+ client.datacenters
532
+ @client = client
533
+ rescue => e
534
+ if e.message =~ /SSL_connect.*certificate verify failed/ ||
535
+ e.message =~ /Peer certificate cannot be authenticated with given CA certificates/ ||
536
+ e.message =~ /SSL peer certificate or SSH remote key was not OK/
537
+ raise Foreman::FingerprintException.new(
538
+ N_("The remote system presented a public key signed by an unidentified certificate authority. If you are sure the remote system is authentic, go to the compute resource edit page, press the 'Test Connection' or 'Load Datacenters' button and submit"),
539
+ ca_cert
540
+ )
541
+ else
542
+ raise e
543
+ end
544
+ end
545
+
546
+ def update_public_key(options = {})
547
+ return unless public_key.blank? || options[:force]
548
+ client
549
+ rescue Foreman::FingerprintException => e
550
+ self.public_key = e.fingerprint if public_key.blank?
551
+ end
552
+
553
+ def api_version
554
+ @api_version ||= client.api_version
555
+ end
556
+
557
+ def ca_cert_store(certs)
558
+ return if certs.blank?
559
+ store = OpenSSL::X509::Store.new
560
+ certs.split(/(?=-----BEGIN)/).each do |cert|
561
+ store.add_cert(OpenSSL::X509::Certificate.new(cert))
562
+ end
563
+ store
564
+ rescue => e
565
+ raise _("Failed to create X509 certificate, error: %s" % e.message)
566
+ end
567
+
568
+ def fetch_unverified(path, query = '')
569
+ ca_url = URI.parse(url)
570
+ ca_url.path = path
571
+ ca_url.query = query
572
+ http = Net::HTTP.new(ca_url.host, ca_url.port)
573
+ http.use_ssl = (ca_url.scheme == 'https')
574
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
575
+ request = Net::HTTP::Get.new(ca_url)
576
+ response = http.request(request)
577
+ # response might be 404 or some other normal code,
578
+ # that would not trigger any exception so we rather check what kind of response we got
579
+ response.is_a?(Net::HTTPSuccess) ? response.body : nil
580
+ rescue => e
581
+ Foreman::Logging.exception("Unable to fetch CA certificate on path #{path}: #{e}", e)
582
+ nil
583
+ end
584
+
585
+ def ca_cert
586
+ fetch_unverified("/ovirt-engine/services/pki-resource", "resource=ca-certificate&format=X509-PEM-CA") || fetch_unverified("/ca.crt")
587
+ end
588
+
589
+ private
590
+
591
+ def update_available_operating_systems
592
+ return false if errors.any?
593
+ ovirt_operating_systems = client.operating_systems if client.respond_to?(:operating_systems)
594
+
595
+ attrs[:available_operating_systems] = ovirt_operating_systems.map do |os|
596
+ { :id => os.id, :name => os.name, :href => os.href }
597
+ end
598
+ rescue Foreman::FingerprintException
599
+ logger.info "Unable to verify OS capabilities, SSL certificate verification failed"
600
+ true
601
+ rescue Fog::Ovirt::Errors::OvirtEngineError => e
602
+ if e.message =~ /404/
603
+ attrs[:available_operating_systems] ||= :unsupported
604
+ else
605
+ raise e
606
+ end
607
+ end
608
+
609
+ def os_name_mapping(host)
610
+ (host.operatingsystem.name =~ /redhat|centos/i) ? 'rhel' : host.operatingsystem.name.downcase
611
+ end
612
+
613
+ def arch_name_mapping(host)
614
+ return unless host.architecture
615
+ (host.architecture.name == 'x86_64') ? 'x64' : host.architecture.name.downcase
616
+ end
617
+
618
+ def default_iface_name(interfaces)
619
+ nic_name_num = 1
620
+ name_blacklist = interfaces.map { |i| i[:name] }.reject { |n| n.blank? }
621
+ nic_name_num += 1 while name_blacklist.include?("nic#{nic_name_num}")
622
+ "nic#{nic_name_num}"
623
+ end
624
+
625
+ def create_interfaces(vm, attrs, cluster_id)
626
+ # first remove all existing interfaces
627
+ vm.interfaces&.each do |interface|
628
+ # The blocking true is a work-around for ovirt bug, it should be removed.
629
+ vm.destroy_interface(:id => interface.id, :blocking => true)
630
+ end
631
+ # add interfaces
632
+ cluster_networks = networks(:cluster_id => cluster_id)
633
+ profiles = vnic_profiles
634
+ interfaces = nested_attributes_for :interfaces, attrs
635
+ interfaces.map do |interface|
636
+ interface[:name] = default_iface_name(interfaces) if interface[:name].empty?
637
+ raise Foreman::Exception.new("Interface network or vnic profile are missing.") if (interface[:network].nil? && interface[:vnic_profile].nil?)
638
+ interface[:network] = get_ovirt_id(cluster_networks, 'network', interface[:network]) if interface[:network].present?
639
+ interface[:vnic_profile] = get_ovirt_id(profiles, 'vnic profile', interface[:vnic_profile]) if interface[:vnic_profile].present?
640
+ if interface[:network].present? && interface[:vnic_profile].present? && profiles.none? { |profile| profile.network.id == interface[:network] }
641
+ raise Foreman::Exception.new("Vnic Profile have a different network")
642
+ end
643
+ vm.add_interface(interface)
644
+ end
645
+ vm.interfaces.reload
646
+ end
647
+
648
+ def create_volumes(vm, attrs)
649
+ # add volumes
650
+ volumes = nested_attributes_for :volumes, attrs
651
+ volumes.map do |vol|
652
+ if vol[:id].blank?
653
+ set_preallocated_attributes!(vol, vol[:preallocate])
654
+ vol[:wipe_after_delete] = to_fog_ovirt_boolean(vol[:wipe_after_delete])
655
+ vol[:storage_domain] = get_ovirt_id(storage_domains, 'storage domain', vol[:storage_domain])
656
+ # The blocking true is a work-around for ovirt bug fixed in ovirt version 5.1
657
+ # The BZ in ovirt cause to the destruction of a host in foreman to fail in case a volume is locked
658
+ # Here we are enforcing blocking behavior which will wait until the volume is added
659
+ vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 5.1}.merge(vol))
660
+ end
661
+ end
662
+ vm.volumes.reload
663
+ end
664
+
665
+ def to_fog_ovirt_boolean(val)
666
+ case val
667
+ when '1'
668
+ 'true'
669
+ when '0'
670
+ 'false'
671
+ else
672
+ val
673
+ end
674
+ end
675
+
676
+ def set_preallocated_attributes!(volume_attributes, preallocate)
677
+ if preallocate == '1'
678
+ volume_attributes[:sparse] = 'false'
679
+ volume_attributes[:format] = 'raw'
680
+ else
681
+ volume_attributes[:sparse] = 'true'
682
+ end
683
+ end
684
+
685
+ def update_interfaces(vm, attrs)
686
+ interfaces = nested_attributes_for :interfaces, attrs
687
+ interfaces.each do |interface|
688
+ vm.destroy_interface(:id => interface[:id]) if interface[:_delete] == '1' && interface[:id]
689
+ if interface[:id].blank?
690
+ interface[:name] = default_iface_name(interfaces) if interface[:name].empty?
691
+ vm.add_interface(interface)
692
+ end
693
+ end
694
+ end
695
+
696
+ def update_volumes(vm, attrs)
697
+ volumes = nested_attributes_for :volumes, attrs
698
+ volumes.each do |volume|
699
+ vm.destroy_volume(:id => volume[:id], :blocking => api_version.to_f < 3.1) if volume[:_delete] == '1' && volume[:id].present?
700
+ vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(volume)) if volume[:id].blank?
701
+ end
702
+ end
703
+
704
+ def set_vm_interfaces_attributes(vm, vm_attrs)
705
+ if vm.respond_to?(:interfaces)
706
+ interfaces = vm.interfaces || []
707
+ vm_attrs[:interfaces_attributes] = interfaces.each_with_index.each_with_object({}) do |(interface, index), hsh|
708
+ interface_attrs = {
709
+ mac: interface.mac,
710
+ compute_attributes: {
711
+ name: interface.name,
712
+ network: interface.network,
713
+ interface: interface.interface,
714
+ vnic_profile: interface.vnic_profile,
715
+ },
716
+ }
717
+ hsh[index.to_s] = interface_attrs
718
+ end
719
+ end
720
+ vm_attrs
721
+ end
722
+
723
+ def get_template_volumes(vm_attrs)
724
+ return {} unless vm_attrs[:template]
725
+ template = template(vm_attrs[:template])
726
+ return {} unless template
727
+ return {} if template.volumes.nil?
728
+ template.volumes.index_by(&:name)
729
+ end
730
+
731
+ def volume_to_attributes(volume, template_volumes)
732
+ {
733
+ size_gb: (volume.size.to_i / 1.gigabyte),
734
+ storage_domain: volume.storage_domain,
735
+ preallocate: (volume.sparse == 'true') ? '0' : '1',
736
+ wipe_after_delete: volume.wipe_after_delete,
737
+ interface: volume.interface,
738
+ bootable: volume.bootable,
739
+ id: template_volumes.fetch(volume.name, nil)&.id,
740
+ }
741
+ end
742
+
743
+ def set_vm_volumes_attributes(vm, vm_attrs)
744
+ template_volumes = get_template_volumes(vm_attrs)
745
+ vm_volumes = vm.volumes || []
746
+ vm_attrs[:volumes_attributes] = vm_volumes.each_with_index.each_with_object({}) do |(volume, index), volumes|
747
+ volumes[index.to_s] = volume_to_attributes(volume, template_volumes)
748
+ end
749
+ vm_attrs
750
+ end
751
+ end
752
+ end