foreman_hyperv 0.0.4 → 0.1.1

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1451 -12
  3. data/README.md +2 -0
  4. data/app/assets/javascripts/foreman_hyperv/compute_resource_base.js +78 -0
  5. data/app/helpers/foreman_hyperv/compute_resources_vms_helper.rb +23 -0
  6. data/app/models/concerns/fog_extensions/hyperv/compute.rb +8 -0
  7. data/app/models/concerns/fog_extensions/hyperv/hard_drive.rb +39 -0
  8. data/app/models/concerns/fog_extensions/hyperv/network_adapter.rb +115 -16
  9. data/app/models/concerns/fog_extensions/hyperv/server.rb +95 -39
  10. data/app/models/concerns/foreman_hyperv/host_managed_extensions.rb +65 -0
  11. data/app/models/foreman_hyperv/hyperv.rb +374 -143
  12. data/app/views/compute_resources/form/_hyperv.html.erb +6 -2
  13. data/app/views/compute_resources_vms/form/hyperv/_base.html.erb +36 -25
  14. data/app/views/compute_resources_vms/form/hyperv/_network.html.erb +34 -11
  15. data/app/views/compute_resources_vms/form/hyperv/_volume.html.erb +5 -2
  16. data/app/views/compute_resources_vms/index/_hyperv.html.erb +2 -2
  17. data/app/views/compute_resources_vms/show/_hyperv.html.erb +27 -9
  18. data/lib/foreman_hyperv/engine.rb +38 -17
  19. data/lib/foreman_hyperv/version.rb +3 -1
  20. data/lib/foreman_hyperv.rb +2 -0
  21. data/lib/tasks/foreman_hyperv_tasks.rake +33 -0
  22. metadata +54 -38
  23. data/.gitignore +0 -9
  24. data/.rubocop.yml +0 -22
  25. data/.rubocop_todo.yml +0 -20
  26. data/.travis.yml +0 -5
  27. data/CHANGELOG.md +0 -25
  28. data/Gemfile +0 -6
  29. data/app/assets/javascripts/compute_resources/hyperv/base.js +0 -32
  30. data/app/models/concerns/fog_extensions/hyperv/vhd.rb +0 -12
  31. data/foreman_hyperv.gemspec +0 -27
  32. data/test/foreman_hyperv_test.rb +0 -11
  33. data/test/test_helper.rb +0 -4
@@ -1,24 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ForemanHyperv
2
4
  class Hyperv < ::ComputeResource
3
- validates :url, :user, :password, presence: true
5
+ include ComputeResourceCaching
4
6
 
5
- def capabilities
6
- [:build]
7
- end
7
+ validates :url, :user, :password, presence: true
8
+ after_validation :validate_connectivity unless Rails.env.test?
8
9
 
9
10
  def self.provider_friendly_name
10
11
  'Hyper-V'
11
12
  end
12
13
 
14
+ def to_label
15
+ "#{name} (#{provider_friendly_name})"
16
+ end
17
+
18
+ def self.available?
19
+ Fog::Compute.providers.include?(:hyperv)
20
+ end
21
+
13
22
  def self.model_name
14
23
  ComputeResource.model_name
15
24
  end
16
25
 
26
+ def supports_update?
27
+ true
28
+ end
29
+
30
+ def editable_network_interfaces?
31
+ true
32
+ end
33
+
34
+ def capabilities
35
+ [:build]
36
+ end
37
+
17
38
  def test_connection(options = {})
18
- super
39
+ validate_connectivity(options)
40
+ end
41
+
42
+ def validate_connectivity(_options = {})
43
+ return unless connection_properties_valid?
44
+ return false if errors.any?
45
+
19
46
  client.valid?
20
47
  rescue Fog::Hyperv::Errors::ServiceError, ArgumentError, WinRM::WinRMAuthorizationError => e
21
- errors[:base] << e.message
48
+ errors.add(:base, e.message)
49
+ end
50
+
51
+ def connection_valid?
52
+ return false if url.blank? || user.blank? || password.blank?
53
+
54
+ client&.valid?
55
+ rescue StandardError
56
+ false
57
+ end
58
+
59
+ def connection_properties_valid?
60
+ errors[:url].empty? && errors[:user].empty? && errors[:password].empty?
61
+ end
62
+
63
+ def caching_enabled
64
+ true
22
65
  end
23
66
 
24
67
  def provided_attributes
@@ -26,94 +69,152 @@ module ForemanHyperv
26
69
  end
27
70
 
28
71
  # TODO
29
- def max_cpu_count
30
- hypervisor.logical_processor_count
72
+ def max_cpu_count(host = nil)
73
+ (host || hypervisor).logical_processor_count
31
74
  end
32
75
 
33
- def max_memory
34
- hypervisor.memory_capacity
76
+ def max_memory(host = nil)
77
+ (host || hypervisor).memory_capacity
35
78
  end
36
79
 
37
- def associated_host(vm)
38
- associate_by('mac', vm.clean_mac_addresses)
80
+ def available_hypervisors
81
+ client.hosts.load(
82
+ cache.cache(:available_hypervisor) do
83
+ client.hosts.all.map(&:attributes)
84
+ end
85
+ )
39
86
  end
87
+ alias hosts available_hypervisors
40
88
 
41
- def new_vm(attr = {})
42
- vm = super
43
- interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
44
- interfaces.map { |i| vm.interfaces << new_interface(i) }
45
- volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
46
- volumes.map { |v| vm.volumes << new_volume(v) }
47
- vm
48
- end
89
+ def cluster(name)
90
+ return nil unless name
49
91
 
50
- def stop_vm(uuid)
51
- find_vm_by_uuid(uuid).stop force: true
92
+ client.clusters.get name
52
93
  end
53
94
 
54
- def create_vm(args = {})
55
- args = vm_instance_defaults.merge(args.to_hash.deep_symbolize_keys)
56
- client.logger.debug "Creating a VM with arguments; #{args}"
95
+ def clusters
96
+ client.clusters.load(
97
+ cache.cache(:clusters) do
98
+ _clusters.map(&:attributes)
99
+ end
100
+ )
101
+ end
57
102
 
58
- args[:computer_name] = args[:computer_name].presence || '.'
103
+ def hypervisor
104
+ client.hosts.new(
105
+ cache.cache(:hypervisor) do
106
+ client.hosts.first.attributes
107
+ end
108
+ )
109
+ end
59
110
 
60
- pre_create = {
61
- boot_device: 'NetworkAdapter',
62
- computer_name: args[:computer_name],
63
- generation: args[:generation].to_i,
64
- memory_startup: args[:memory_startup].presence.to_i,
65
- name: args[:name],
66
- no_vhd: true
67
- }
111
+ delegate :servers, to: :client
68
112
 
69
- vm = client.servers.create pre_create
113
+ def switches(host)
114
+ host ||= hosts.first
115
+ client.switches.load(
116
+ cache.cache(:"#{host.name}-switches") do
117
+ host.switches.all.map(&:attributes)
118
+ end
119
+ )
120
+ end
70
121
 
71
- post_save = {
72
- dynamic_memory_enabled: Foreman::Cast.to_bool(args[:dynamic_memory_enabled]),
73
- memory_minimum: args[:memory_minimum].presence.to_i,
74
- memory_maximum: args[:memory_maximum].presence.to_i,
75
- notes: args[:notes].presence,
76
- processor_count: args[:processor_count].to_i
77
- }
78
- post_save.each do |k, v|
79
- vm.send("#{k}=".to_sym, v)
122
+ def new_vm(attr = {})
123
+ attr.delete :id
124
+ vm = super
125
+ iface_nested_attrs = nested_attributes_for :interfaces, attr[:interfaces_attributes]
126
+ vm.network_adapters = iface_nested_attrs.map do |attr|
127
+ attr.delete :id
128
+ Fog::Hyperv::Compute::NetworkAdapter.new(service: vm.service, vm: vm).tap do |nic|
129
+ attr.select { |_, v| v.present? }.each do |k, v|
130
+ nic.send(:"#{k}=", v)
131
+ end
132
+ end
80
133
  end
134
+ volume_nested_attrs = nested_attributes_for :volumes, attr[:volumes_attributes]
135
+ vm.hard_drives = volume_nested_attrs.map do |attr|
136
+ attr.delete :id
137
+ Fog::Hyperv::Compute::HardDrive.new(service: vm.service, vm: vm).tap do |hdd|
138
+ attr.select { |_, v| v.present? }.each do |k, v|
139
+ hdd.send(:"#{k}=", v)
140
+ end
141
+ end
142
+ end
143
+ vm.id = nil
144
+ vm
145
+ end
81
146
 
82
- vm.save if vm.dirty?
147
+ def create_vm(attr = {})
148
+ attr = vm_instance_defaults.merge(attr.to_hash.deep_symbolize_keys)
149
+ attr.delete :computer_name if attr[:computer_name].blank?
150
+ attr.delete :start
151
+
152
+ validate_vm(attr, new: true)
153
+ validate_interfaces(attr)
154
+ validate_volumes(attr)
155
+
156
+ vm = client.servers.new(
157
+ name: attr[:name],
158
+ computer_name: attr[:computer_name],
159
+ generation: attr[:generation],
160
+ dynamic_memory_enabled: Foreman::Cast.to_bool(attr[:dynamic_memory_enabled]),
161
+ memory_startup: attr[:memory_startup].to_i,
162
+ memory_minimum: attr[:memory_minimum].to_i,
163
+ memory_maximum: attr[:memory_maximum].to_i,
164
+ processor_count: attr[:processor_count].to_i,
165
+ notes: attr[:notes]
166
+ )
167
+ # TODO: Allow configuring boot device?
168
+ vm.create boot_device: :NetworkAdapter
83
169
 
84
- if vm.generation == 2 && args[:secure_boot_enabled].present?
170
+ if vm.generation == :UEFI && attr[:secure_boot_enabled].present?
85
171
  f = vm.firmware
86
- f.secure_boot = Foreman::Cast.to_bool(args[:secure_boot_enabled]) ? :On : :Off
172
+ f.secure_boot = Foreman::Cast.to_bool(attr[:secure_boot_enabled]) ? :On : :Off
87
173
  f.save if f.dirty?
88
174
  end
89
175
 
90
- create_interfaces(vm, args[:interfaces_attributes])
91
- create_volumes(vm, args[:volumes_attributes])
92
-
93
- vm.set_vlan(args[:vlan].to_i) if args[:vlan].presence && vm.respond_to?(:set_vlan)
176
+ create_interfaces(vm, attr)
177
+ create_volumes(vm, attr)
94
178
 
179
+ vm.start if attr[:start] == '1'
95
180
  vm
96
181
  rescue StandardError => e
97
- vm.stop turn_off: true
182
+ if vm
183
+ vm.stop
184
+ vm.hard_drives.each { |hdd| hdd.destroy(underlying: true) }
185
+ vm.destroy
186
+ end
98
187
 
99
188
  raise e
100
189
  end
101
190
 
102
- def save_vm(uuid, attr)
191
+ def save_vm(uuid, web_attr)
192
+ attr = web_attr.deep_symbolize_keys
193
+
194
+ validate_vm(attr)
195
+ validate_interfaces(attr)
196
+ validate_volumes(attr)
197
+
198
+ attr.delete :start
199
+
103
200
  vm = find_vm_by_uuid(uuid)
104
- client.logger.debug "Saving a VM with arguments; #{attr}"
105
- attr.each do |k, v|
106
- vm.send("#{k}=".to_sym, v) if vm.respond_to?("#{k}=".to_sym)
201
+ logger.debug "Updating VM #{vm} with arguments; #{attr}"
202
+
203
+ vm.processor_count = attr[:processor_count].to_i
204
+ vm.notes = attr[:notes].presence
205
+ vm.dynamic_memory_enabled = Foreman::Cast.to_bool(attr[:dynamic_memory_enabled])
206
+ if vm.dynamic_memory_enabled
207
+ vm.memory_minimum = attr[:memory_minimum].to_i
208
+ vm.memory_maximum = attr[:memory_maximum].to_i
107
209
  end
108
-
109
- if vm.generation == 2 && attr[:secure_boot_enabled].present?
210
+ if vm.generation == :UEFI && attr[:secure_boot_enabled].present?
110
211
  f = vm.firmware
111
212
  f.secure_boot = Foreman::Cast.to_bool(attr[:secure_boot_enabled]) ? :On : :Off
112
213
  f.save if f.dirty?
113
214
  end
114
215
 
115
- update_interfaces(vm, attr[:interfaces_attributes])
116
- update_volumes(vm, attr[:volumes_attributes])
216
+ update_interfaces(vm, attr)
217
+ update_volumes(vm, attr)
117
218
 
118
219
  vm.save if vm.dirty?
119
220
  vm
@@ -121,10 +222,8 @@ module ForemanHyperv
121
222
 
122
223
  def destroy_vm(uuid)
123
224
  vm = find_vm_by_uuid(uuid)
124
- vm.stop force: true if vm.ready?
125
- vm.hard_drives.each do |hd|
126
- hd.vhd.destroy if hd.path
127
- end
225
+ vm.stop turn_off: true
226
+ vm.hard_drives.each { |hdd| hdd.destroy(underlying: true) }
128
227
  # TODO: Remove the empty VM folder
129
228
  vm.destroy
130
229
  rescue ActiveRecord::RecordNotFound, Fog::Errors::NotFound
@@ -132,51 +231,105 @@ module ForemanHyperv
132
231
  true
133
232
  end
134
233
 
135
- def new_interface(attr = {})
136
- client.network_adapters.new attr
137
- end
234
+ def update_required?(old_attrs, new_attrs)
235
+ new_attrs.deep_symbolize_keys[:volumes_attributes]&.each_value do |hdd|
236
+ if hdd[:id].present? && hdd[:_delete] == '1'
237
+ Rails.logger.debug 'Scheduling compute instance update because a volume was removed'
238
+ return true
239
+ elsif hdd[:id].blank? && hdd[:_delete] != '1'
240
+ Rails.logger.debug 'Scheduling compute instance update because a new volume was added'
241
+ return true
242
+ end
243
+ end
244
+ new_attrs.deep_symbolize_keys[:interfaces_attributes]&.each_value do |iface|
245
+ if iface[:id].present? && iface[:_destroy] == '1'
246
+ Rails.logger.debug 'Scheduling compute instance update because an interface was removed'
247
+ return true
248
+ elsif iface[:id].blank? && iface[:_destroy] != '1'
249
+ Rails.logger.debug 'Scheduling compute instance update because a new interface was added'
250
+ return true
251
+ end
252
+ end
138
253
 
139
- def new_volume(attr = {})
140
- client.vhds.new attr
141
- end
254
+ deep_update_required = proc do |old, new|
255
+ old.merge(new) do |k, old_v, new_v|
256
+ if %i[allowed_vlan_ids secondary_vlan_ids].include?(k)
257
+ tmp = Fog::Hyperv::Compute::NetworkAdapter.new
142
258
 
143
- def new_cdrom(attr = {})
144
- client.dvd_drives.new attr
259
+ old_v = Fog::Hyperv::Compute::NetworkAdapterVlan.render_vlan_list(tmp.send(:parse_vlan_list, old_v.to_s))
260
+ new_v = Fog::Hyperv::Compute::NetworkAdapterVlan.render_vlan_list(tmp.send(:parse_vlan_list, new_v.to_s))
261
+ end
262
+
263
+ if old_v.is_a?(Hash) && new_v.is_a?(Hash)
264
+ deep_update_required.call(old_v, new_v)
265
+ elsif old_v.to_s != new_v.to_s
266
+ Rails.logger.debug do
267
+ "Scheduling compute instance update because #{k} changed it's value from '#{old_v}' (#{old_v.class}) to '#{new_v}' (#{new_v.class})"
268
+ end
269
+ return true
270
+ end
271
+ new_v
272
+ end
273
+ end
274
+ deep_update_required.call(old_attrs.deep_symbolize_keys, new_attrs.deep_symbolize_keys)
275
+
276
+ false
145
277
  end
146
278
 
147
- def editable_network_interfaces?
148
- true
279
+ # def console(uuid)
280
+ # vm = find_vm_by_uuid(uuid)
281
+ #
282
+ # {
283
+ # type: 'rdp',
284
+ # host: vm.computer.fully_qualified_domain_name,
285
+ # }
286
+ # end
287
+
288
+ def new_interface(attr = {})
289
+ Fog::Hyperv::Compute::NetworkAdapter.new attr
149
290
  end
150
291
 
151
- def switches
152
- client.switches.all # _quick_query: true
292
+ def new_volume(attr = {})
293
+ basename = attr.delete(:basename) { 'Disk' }
294
+ size = attr.delete(:size)
295
+
296
+ vhd = client.vhds.new({ basename: basename, size: size }.compact)
297
+ Fog::Hyperv::Compute::HardDrive.new vhd: vhd, **attr
153
298
  end
154
299
 
155
- def supports_update?
156
- true
300
+ def new_cdrom(attr = {})
301
+ Fog::Hyperv::Compute::DvdDrive.new attr
157
302
  end
158
303
 
159
- def available_hypervisors
160
- client.hosts
304
+ def vm_instance_defaults
305
+ super.merge(
306
+ generation: 2,
307
+ memory_startup: 1024.megabytes,
308
+ processor_count: 1,
309
+ interfaces: [new_interface]
310
+ )
161
311
  end
162
- alias hosts available_hypervisors
163
312
 
164
- def clusters
165
- if client.respond_to? :supports_clusters?
166
- return [] unless client.supports_clusters?
313
+ def set_vm_volumes_attributes(vm, vm_attrs)
314
+ volumes = vm.hard_drives || []
315
+ vm_attrs[:volumes_attributes] = volumes.each_with_index.to_h do |volume, index|
316
+ [index.to_s, volume.compute_attributes]
167
317
  end
168
-
169
- client.clusters
170
- rescue
171
- []
318
+ vm_attrs
172
319
  end
173
320
 
174
- def hypervisor
175
- client.hosts.first
321
+ def set_vm_interfaces_attributes(vm, vm_attrs)
322
+ interfaces = vm.interfaces || []
323
+ vm_attrs[:interfaces_attributes] = interfaces.each_with_index.to_h do |interface, index|
324
+ interface_attrs = {
325
+ mac: interface.mac,
326
+ compute_attributes: interface.compute_attributes
327
+ }
328
+ [index.to_s, interface_attrs]
329
+ end
330
+ vm_attrs
176
331
  end
177
332
 
178
- delegate :servers, to: :client
179
-
180
333
  protected
181
334
 
182
335
  def client
@@ -188,85 +341,163 @@ module ForemanHyperv
188
341
  )
189
342
  end
190
343
 
191
- def vm_instance_defaults
192
- super.merge(
193
- generation: 1,
194
- memory_startup: 512.megabytes,
195
- processor_count: 1,
196
- boot_device: 'NetworkAdapter'
197
- )
344
+ private
345
+
346
+ def _clusters
347
+ return [] if client.respond_to?(:supports_clusters?) && !client.supports_clusters?
348
+
349
+ client.clusters.all
350
+ rescue StandardError
351
+ []
198
352
  end
199
353
 
200
- def create_interfaces(vm, attrs)
201
- vm.network_adapters.each(&:destroy)
354
+ def validate_vm(attr, new: false)
355
+ # logger.debug "Validate VM #{attr.inspect}"
356
+ raise Foreman::Exception, 'VM lacks generation' if new && attr[:generation].blank?
357
+ raise Foreman::Exception, 'VM lacks memory' unless attr[:memory_startup].to_i.positive?
358
+ raise Foreman::Exception, 'VM lacks CPUs' unless attr[:processor_count].to_i.positive?
202
359
 
203
- interfaces = nested_attributes_for :interfaces, attrs
204
- client.logger.debug "Building interfaces with: #{interfaces}"
360
+ return unless Foreman::Cast.to_bool(attr[:dynamic_memory_enabled])
361
+ raise Foreman::Exception, 'VM lacks memory minimum' unless attr[:memory_minimum].to_i.positive?
362
+ raise Foreman::Exception, 'VM lacks memory maximum' unless attr[:memory_maximum].to_i.positive?
363
+ end
364
+
365
+ def validate_interfaces(attr)
366
+ interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
367
+ interfaces.reject! { |iface| iface[:_destroy] == '1' }
368
+ # logger.debug "Validate NIC #{interfaces.inspect}"
205
369
  interfaces.each do |iface|
206
- nic = vm.network_adapters.new name: iface[:name], switch_name: iface[:network]
207
- if iface[:mac]
208
- nic.mac = iface[:mac]
209
- nic.dynamic_mac_address_enabled = false
370
+ compute = iface[:compute_attributes] || iface
371
+ case compute[:vlan_operation_mode].to_s
372
+ when 'Untagged'
373
+ # No VLAN settings to verify
374
+ when 'Access'
375
+ raise Foreman::Exception, 'Interface is missing access VLAN' unless compute[:access_vlan_id].to_i.positive?
376
+ when 'Trunk'
377
+ raise Foreman::Exception, 'Interface is missing native VLAN' unless compute[:native_vlan_id].to_i.positive?
378
+ raise Foreman::Exception, 'Interface is missing allowed VLANs' if compute[:allowed_vlan_ids].blank?
379
+ when 'Private'
380
+ raise Foreman::Exception, 'Interface is missing primary VLAN' unless compute[:primary_vlan_id].to_i.positive?
381
+
382
+ case compute[:vlan_private_mode].to_s
383
+ when 'Promiscuous'
384
+ if compute[:secondary_vlan_ids].blank?
385
+ raise Foreman::Exception,
386
+ 'Interface is missing secondary VLANs'
387
+ end
388
+ else
389
+ unless compute[:secondary_vlan_id].to_i.positive?
390
+ raise Foreman::Exception,
391
+ 'Interface is missing secondary VLAN'
392
+ end
393
+ end
394
+ else
395
+ raise Foreman::Exception, 'Interface has unknown VLAN mode'
210
396
  end
211
- if vm.generation == 1 && iface[:type].present?
212
- nic.is_legacy = Foreman::Cast.to_bool(iface[:type])
397
+ end
398
+ end
399
+
400
+ def create_interfaces(vm, attr)
401
+ interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
402
+ logger.debug "Creating interfaces with: #{interfaces}"
403
+
404
+ first_provisioned = false
405
+ interfaces.each do |iface|
406
+ # The VM is pre-created with one NIC regardless of given creation options, so configure that one first
407
+ nic = vm.network_adapters.first unless first_provisioned
408
+ first_provisioned = true
409
+
410
+ nic ||= vm.network_adapters.new
411
+ iface.except(:identity, :ip, :ip6).each do |k, v|
412
+ nic.send(:"#{k}=", v.presence)
213
413
  end
414
+ # nic.is_legacy = Foreman::Cast.to_bool(iface[:is_legacy]) if vm.generation == :BIOS && iface[:is_legacy].present?
415
+ # logger.debug "Creating interface with #{iface.inspect} - #{nic.inspect}\n#{nic.vlan_setting.inspect}"
214
416
  nic.save
215
417
  end
216
418
 
217
- # Populate the MAC addresses
419
+ unless vm.network_adapters.all(_return_fields: %i[dynamic_mac_address_enabled]).any?(&:dynamic_mac_address_enabled)
420
+ return
421
+ end
422
+
423
+ # Populate all non-populated MAC addresses
218
424
  vm.start
219
425
  vm.stop turn_off: true
220
426
 
221
- vm.network_adapters.reload
222
- vm.network_adapters.each do |nic|
427
+ vm.network_adapters.reload.each do |nic|
223
428
  nic.dynamic_mac_address_enabled = false
224
429
  nic.save if nic.dirty?
225
430
  end
226
431
  end
227
432
 
228
- def create_volumes(vm, attrs)
229
- volumes = nested_attributes_for :volumes, attrs
230
- client.logger.debug "Building volumes with: #{volumes}"
231
- volumes.each do |vol|
232
- vhd = vm.vhds.create path: vm.folder_name + '\\' + vol[:path], size: vol[:size]
233
- vm.hard_drives.create path: vhd.path
234
- end
235
- vm.hard_drives.reload
236
- end
433
+ def update_interfaces(vm, attr)
434
+ interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
435
+ logger.debug "Updating interfaces with: #{interfaces}"
237
436
 
238
- def update_interfaces(vm, attrs)
239
- interfaces = nested_attributes_for :interfaces, attrs
240
- client.logger.debug "Updating interfaces with: #{interfaces}"
241
437
  interfaces.each do |interface|
242
- if interface[:id].blank? && interface[:_delete] != '1'
243
- nic = vm.network_adapters.create interface
244
- nic.dynamic_mac_address_enabled = false if nic.mac
245
- nic.save
246
- elsif interface[:id].present?
247
- nic = vm.network_adapters.find { |n| n.id == interface[:id] }
248
- if interface[:_delete] == '1'
249
- nic.delete
438
+ compute = interface[:compute_attributes]
439
+ if compute[:identity].present?
440
+ nic = vm.network_adapters.get compute[:identity]
441
+ if interface[:_destroy] == '1'
442
+ nic.destroy
250
443
  else
251
- interface.each do |k, v|
252
- nic.send("#{k}=".to_sym, v)
444
+ compute.each do |k, v|
445
+ nic.send(:"#{k}=", v.presence)
253
446
  end
254
- nic.save if nic.dirty?
447
+ nic.mac ||= interface[:mac].presence
448
+ nic.save
255
449
  end
450
+ elsif interface[:_destroy] != '1'
451
+ nic = vm.network_adapters.new
452
+ compute.each do |k, v|
453
+ nic.send(:"#{k}=", v.presence)
454
+ end
455
+ nic.mac ||= interface[:mac].presence
456
+ nic.save
256
457
  end
257
458
  end
258
459
  end
259
460
 
260
- def update_volumes(vm, attrs)
261
- volumes = nested_attributes_for :volumes, attrs
262
- client.logger.debug "Updating volumes with: #{volumes}"
461
+ def validate_volumes(attr)
462
+ volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
463
+ volumes.reject! { |vol| vol[:_delete] == '1' }
464
+ # logger.debug "Validate Volume #{volumes.inspect}"
465
+ volumes.each do |vol|
466
+ raise Foreman::Exception, 'Volume lacks name' if vol[:basename].blank?
467
+ raise Foreman::Exception, 'Volume name should not include a file extension' if vol[:basename] =~ /\.vhd[sx]?$/
468
+ raise Foreman::Exception, 'Volume lacks size' unless vol[:size_bytes].to_i.positive?
469
+ end
470
+ return unless volumes.group_by { |vol| vol[:basename].downcase }.any? { |_k, v| v.many? }
471
+
472
+ raise Foreman::Exception, 'Volume names need to be unique'
473
+ end
474
+
475
+ def create_volumes(vm, attr)
476
+ volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
477
+ logger.debug "Creating volumes with: #{volumes}"
478
+ volumes.each do |vol|
479
+ vhd = vm.vhds.create basename: vol[:basename], size: vol[:size_bytes].to_i
480
+ vm.hard_drives.create vhd: vhd
481
+ end
482
+ end
483
+
484
+ def update_volumes(vm, attr)
485
+ volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
486
+ logger.debug "Updating volumes with: #{volumes}"
263
487
  volumes.each do |volume|
264
- if volume[:_delete] == '1' && volume[:id].present?
265
- hd = vm.hard_drives.find { |h| h.id == volume[:id] }
266
- hd.vhd.destroy
267
- hd.destroy
488
+ if volume[:id].present?
489
+ hd = vm.hard_drives.get volume[:id]
490
+ if volume[:_delete] == '1'
491
+ hd.destroy(underlying: true)
492
+ else
493
+ vhd = hd.vhd
494
+ vhd.size = volume[:size_bytes].to_i
495
+ vhd.save if vhd.dirty?
496
+ end
497
+ elsif volume[:_delete] != '1'
498
+ vhd = vm.vhds.create basename: volume[:basename], size: volume[:size_bytes].to_i
499
+ vm.hard_drives.create path: vhd.path
268
500
  end
269
- vm.hard_drives.create(path: volume[:path], size: volume[:size]) if volume[:id].blank? && volume[:_delete] != '1'
270
501
  end
271
502
  end
272
503
  end
@@ -2,6 +2,10 @@
2
2
  <%= text_f f, :user, label: _('User') %>
3
3
  <%= password_f f, :password, label: _('Password') %>
4
4
 
5
- <%= link_to_function _("Test Connection"), "testConnection(this)", :class => "btn + #{@compute_resource.test_connection.is_a?(FalseClass) ? "btn-default" : "btn-success"}", :'data-url' => test_connection_compute_resources_path %>
5
+ <%# Currently not working due to marshaling errors %>
6
+ <%#= checkbox_f f, :caching_enabled, :label => _("Enable Caching"),
7
+ :help_inline => _("Cache slow calls to Hyper-V to speed up page rendering") %>
6
8
 
7
- <%= hidden_spinner('', :id => 'test_connection_indicator') %>
9
+ <div class="col-md-offset-2">
10
+ <%= test_connection_button_f(f, f.object.connection_valid?) %>
11
+ </div>