foreman_ovirt 0.2.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.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +140 -0
- data/Rakefile +37 -43
- data/app/assets/javascripts/foreman_ovirt/display.js +10 -0
- data/app/assets/javascripts/foreman_ovirt/host_edit.js +61 -0
- data/app/assets/javascripts/foreman_ovirt/nic_info.js +3 -0
- data/app/assets/javascripts/foreman_ovirt/ovirt.js +261 -0
- data/app/controllers/concerns/foreman_ovirt/compute_resources_vms_controller.rb +29 -0
- data/app/controllers/concerns/foreman_ovirt/parameters_extension.rb +35 -0
- data/app/controllers/foreman_ovirt/concerns/compute_resources_controller_extensions.rb +60 -0
- data/app/helpers/ovirt_compute_resource_helper.rb +26 -0
- data/app/models/concerns/fog_extensions/ovirt/server.rb +42 -0
- data/app/models/concerns/fog_extensions/ovirt/template.rb +14 -0
- data/app/models/concerns/fog_extensions/ovirt/volume.rb +11 -0
- data/app/models/foreman_ovirt/ovirt.rb +752 -0
- data/app/views/api/v2/compute_resources/ovirt.json.rabl +1 -0
- data/app/views/compute_resources/form/_ovirt.html.erb +14 -0
- data/app/views/compute_resources/show/_ovirt.html.erb +16 -0
- data/app/views/compute_resources_vms/form/ovirt/_base.html.erb +83 -0
- data/app/views/compute_resources_vms/form/ovirt/_network.html.erb +32 -0
- data/app/views/compute_resources_vms/form/ovirt/_volume.html.erb +16 -0
- data/app/views/compute_resources_vms/index/_ovirt.html.erb +12 -0
- data/app/views/compute_resources_vms/index/_ovirt_json.erb +6 -0
- data/app/views/compute_resources_vms/show/_ovirt.html.erb +55 -0
- data/app/views/images/form/_ovirt.html.erb +4 -0
- data/config/routes.rb +8 -13
- data/db/migrate/20250810212811_update_legacy_ovirt_compute_resource_type.rb +21 -0
- data/lib/foreman_ovirt/engine.rb +70 -0
- data/lib/foreman_ovirt/version.rb +3 -0
- data/lib/foreman_ovirt.rb +2 -1
- data/lib/tasks/foreman_ovirt_tasks.rake +30 -0
- data/locale/Makefile +73 -0
- data/locale/en/foreman_ovirt.po +19 -0
- data/locale/foreman_ovirt.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/package.json +39 -0
- data/test/factories/foreman_ovirt_factories.rb +5 -0
- data/test/test_plugin_helper.rb +6 -0
- data/test/unit/foreman_ovirt_test.rb +11 -0
- data/webpack/components/extensions/HostDetails/DetailsTabCards/OvirtCard.js +132 -0
- data/webpack/components/ovirt.js +20 -0
- data/webpack/global_index.js +14 -0
- data/webpack/global_test_setup.js +11 -0
- data/webpack/index.js +1 -0
- data/webpack/test_setup.js +17 -0
- metadata +157 -128
- data/.document +0 -5
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -33
- data/LICENSE.txt +0 -20
- data/README.rdoc +0 -20
- data/VERSION +0 -1
- data/app/controllers/foreman_ovirt/auth_source_ovirts_controller.rb +0 -57
- data/app/controllers/foreman_ovirt/dashboard_controller.rb +0 -10
- data/app/controllers/foreman_ovirt/hosts_controller.rb +0 -17
- data/app/models/foreman_ovirt/auth_source_ovirt.rb +0 -78
- data/app/models/foreman_ovirt/user_extensions.rb +0 -26
- data/app/views/foreman_ovirt/auth_source_ovirts/_form.html.erb +0 -17
- data/app/views/foreman_ovirt/auth_source_ovirts/edit.html.erb +0 -3
- data/app/views/foreman_ovirt/auth_source_ovirts/index.html.erb +0 -22
- data/app/views/foreman_ovirt/auth_source_ovirts/new.html.erb +0 -3
- data/app/views/foreman_ovirt/auth_source_ovirts/welcome.html.erb +0 -8
- data/app/views/foreman_ovirt/hosts/_overview.html.erb +0 -27
- data/app/views/foreman_ovirt/hosts/show.html.erb +0 -48
- data/app/views/foreman_ovirt/hosts/show_graphs.html.erb +0 -13
- data/app/views/layouts/application_ovirt.html.erb +0 -36
- data/config/initializers/ovirt_setup.rb +0 -30
- data/foreman_ovirt.gemspec +0 -74
- 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
|