foreman_hyperv 0.0.3 → 0.1.0

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