foreman_hyperv 0.0.4 → 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 +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 +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 +111 -16
  9. data/app/models/concerns/fog_extensions/hyperv/server.rb +87 -38
  10. data/app/models/concerns/foreman_hyperv/host_managed_extensions.rb +66 -0
  11. data/app/models/foreman_hyperv/hyperv.rb +365 -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 +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 -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,119 +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
 
28
- # TODO
29
- def max_cpu_count
30
- hypervisor.logical_processor_count
69
+ def editable_network_interfaces?
70
+ true
31
71
  end
32
72
 
33
- def max_memory
34
- hypervisor.memory_capacity
73
+ # TODO
74
+ def max_cpu_count(host = nil)
75
+ (host || hypervisor).logical_processor_count
35
76
  end
36
77
 
37
- def associated_host(vm)
38
- associate_by('mac', vm.clean_mac_addresses)
78
+ def max_memory(host = nil)
79
+ (host || hypervisor).memory_capacity
39
80
  end
40
81
 
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
82
+ def available_hypervisors
83
+ client.hosts.load(
84
+ cache.cache(:available_hypervisor) do
85
+ client.hosts.all.map(&:attributes)
86
+ end
87
+ )
48
88
  end
89
+ alias hosts available_hypervisors
90
+
91
+ def cluster(name)
92
+ return nil unless name
49
93
 
50
- def stop_vm(uuid)
51
- find_vm_by_uuid(uuid).stop force: true
94
+ client.clusters.get name
52
95
  end
53
96
 
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}"
97
+ def clusters
98
+ client.clusters.load(
99
+ cache.cache(:clusters) do
100
+ _clusters.map(&:attributes)
101
+ end
102
+ )
103
+ end
57
104
 
58
- args[:computer_name] = args[:computer_name].presence || '.'
105
+ def hypervisor
106
+ client.hosts.new(
107
+ cache.cache(:hypervisor) do
108
+ client.hosts.first.attributes
109
+ end
110
+ )
111
+ end
59
112
 
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
- }
113
+ delegate :servers, to: :client
68
114
 
69
- 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
70
123
 
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)
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
80
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
81
148
 
82
- 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
83
171
 
84
- if vm.generation == 2 && args[:secure_boot_enabled].present?
172
+ if vm.generation == :UEFI && attr[:secure_boot_enabled].present?
85
173
  f = vm.firmware
86
- 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
87
175
  f.save if f.dirty?
88
176
  end
89
177
 
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)
178
+ create_interfaces(vm, attr)
179
+ create_volumes(vm, attr)
94
180
 
181
+ vm.start if attr[:start] == '1'
95
182
  vm
96
183
  rescue StandardError => e
97
- 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
98
189
 
99
190
  raise e
100
191
  end
101
192
 
102
- 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
+
103
202
  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)
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
107
211
  end
108
-
109
- if vm.generation == 2 && attr[:secure_boot_enabled].present?
212
+ if vm.generation == :UEFI && attr[:secure_boot_enabled].present?
110
213
  f = vm.firmware
111
214
  f.secure_boot = Foreman::Cast.to_bool(attr[:secure_boot_enabled]) ? :On : :Off
112
215
  f.save if f.dirty?
113
216
  end
114
217
 
115
- update_interfaces(vm, attr[:interfaces_attributes])
116
- update_volumes(vm, attr[:volumes_attributes])
218
+ update_interfaces(vm, attr)
219
+ update_volumes(vm, attr)
117
220
 
118
221
  vm.save if vm.dirty?
119
222
  vm
@@ -121,10 +224,8 @@ module ForemanHyperv
121
224
 
122
225
  def destroy_vm(uuid)
123
226
  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
227
+ vm.stop turn_off: true
228
+ vm.hard_drives.each { |hdd| hdd.destroy(underlying: true) }
128
229
  # TODO: Remove the empty VM folder
129
230
  vm.destroy
130
231
  rescue ActiveRecord::RecordNotFound, Fog::Errors::NotFound
@@ -132,51 +233,103 @@ module ForemanHyperv
132
233
  true
133
234
  end
134
235
 
135
- def new_interface(attr = {})
136
- client.network_adapters.new attr
137
- 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
138
255
 
139
- def new_volume(attr = {})
140
- client.vhds.new attr
141
- 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
142
260
 
143
- def new_cdrom(attr = {})
144
- 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
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:, size: }.compact)
297
+ Fog::Hyperv::Compute::HardDrive.new 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,154 @@ 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
+ if client.respond_to? :supports_clusters?
348
+ return [] unless client.supports_clusters?
349
+ end
350
+
351
+ client.clusters.all
352
+ rescue
353
+ []
198
354
  end
199
355
 
200
- def create_interfaces(vm, attrs)
201
- 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
202
361
 
203
- interfaces = nested_attributes_for :interfaces, attrs
204
- client.logger.debug "Building interfaces with: #{interfaces}"
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
367
+
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}"
205
372
  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
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'
210
391
  end
211
- if vm.generation == 1 && iface[:type].present?
212
- nic.is_legacy = Foreman::Cast.to_bool(iface[:type])
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)
213
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}"
214
411
  nic.save
215
412
  end
216
413
 
217
- # 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
218
417
  vm.start
219
418
  vm.stop turn_off: true
220
419
 
221
- vm.network_adapters.reload
222
- vm.network_adapters.each do |nic|
420
+ vm.network_adapters.reload.each do |nic|
223
421
  nic.dynamic_mac_address_enabled = false
224
422
  nic.save if nic.dirty?
225
423
  end
226
424
  end
227
425
 
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
426
+ def update_interfaces(vm, attr)
427
+ interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
428
+ logger.debug "Updating interfaces with: #{interfaces}"
237
429
 
238
- def update_interfaces(vm, attrs)
239
- interfaces = nested_attributes_for :interfaces, attrs
240
- client.logger.debug "Updating interfaces with: #{interfaces}"
241
430
  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
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
250
436
  else
251
- interface.each do |k, v|
252
- nic.send("#{k}=".to_sym, v)
437
+ compute.each do |k, v|
438
+ nic.send(:"#{k}=", v.presence)
253
439
  end
254
- nic.save if nic.dirty?
440
+ nic.mac ||= interface[:mac].presence
441
+ nic.save
255
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)
447
+ end
448
+ nic.mac ||= interface[:mac].presence
449
+ nic.save
256
450
  end
257
451
  end
258
452
  end
259
453
 
260
- def update_volumes(vm, attrs)
261
- volumes = nested_attributes_for :volumes, attrs
262
- 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}"
263
478
  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
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
268
491
  end
269
- vm.hard_drives.create(path: volume[:path], size: volume[:size]) if volume[:id].blank? && volume[:_delete] != '1'
270
492
  end
271
493
  end
272
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>