opennebula 5.12.8 → 5.13.80.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ActionManager.rb +1 -1
  3. data/lib/CommandManager.rb +1 -1
  4. data/lib/DriverExecHelper.rb +44 -28
  5. data/lib/OpenNebulaDriver.rb +8 -4
  6. data/lib/VirtualMachineDriver.rb +9 -2
  7. data/lib/cloud/CloudClient.rb +3 -3
  8. data/lib/datacenter.rb +1258 -0
  9. data/lib/datastore.rb +1025 -0
  10. data/lib/distributed_firewall.rb +280 -0
  11. data/lib/file_helper.rb +370 -0
  12. data/lib/host.rb +1517 -0
  13. data/lib/logical_port.rb +50 -0
  14. data/lib/logical_switch.rb +77 -0
  15. data/lib/memoize.rb +74 -0
  16. data/lib/models.rb +32 -0
  17. data/lib/models/role.rb +1126 -0
  18. data/lib/models/service.rb +709 -0
  19. data/lib/network.rb +635 -0
  20. data/lib/nsx_client.rb +144 -0
  21. data/lib/nsx_component.rb +28 -0
  22. data/lib/nsx_constants.rb +149 -0
  23. data/lib/nsx_driver.rb +78 -0
  24. data/lib/nsx_error.rb +77 -0
  25. data/lib/nsx_rule.rb +193 -0
  26. data/lib/nsxt_client.rb +176 -0
  27. data/lib/nsxt_dfw.rb +196 -0
  28. data/lib/nsxt_logical_port.rb +94 -0
  29. data/lib/nsxt_rule.rb +188 -0
  30. data/lib/nsxt_tz.rb +38 -0
  31. data/lib/nsxv_client.rb +176 -0
  32. data/lib/nsxv_dfw.rb +202 -0
  33. data/lib/nsxv_logical_port.rb +107 -0
  34. data/lib/nsxv_rule.rb +172 -0
  35. data/lib/nsxv_tz.rb +41 -0
  36. data/lib/opaque_network.rb +134 -0
  37. data/lib/opennebula.rb +5 -2
  38. data/lib/opennebula/acl.rb +1 -1
  39. data/lib/opennebula/acl_pool.rb +1 -1
  40. data/lib/opennebula/client.rb +1 -1
  41. data/lib/opennebula/cluster.rb +1 -1
  42. data/lib/opennebula/cluster_pool.rb +1 -1
  43. data/lib/opennebula/datastore.rb +1 -1
  44. data/lib/opennebula/datastore_pool.rb +1 -1
  45. data/lib/opennebula/document.rb +8 -29
  46. data/lib/opennebula/document_json.rb +42 -12
  47. data/lib/opennebula/document_pool.rb +1 -1
  48. data/lib/opennebula/document_pool_json.rb +1 -1
  49. data/lib/opennebula/error.rb +4 -1
  50. data/lib/opennebula/flow.rb +23 -0
  51. data/lib/opennebula/flow/grammar.rb +1195 -0
  52. data/lib/opennebula/flow/service_pool.rb +190 -0
  53. data/lib/opennebula/flow/service_template.rb +572 -0
  54. data/lib/opennebula/flow/service_template_ext.rb +84 -0
  55. data/lib/opennebula/flow/service_template_pool.rb +32 -0
  56. data/lib/opennebula/flow/validator.rb +499 -0
  57. data/lib/opennebula/group.rb +1 -1
  58. data/lib/opennebula/group_pool.rb +1 -1
  59. data/lib/opennebula/hook.rb +5 -12
  60. data/lib/opennebula/hook_log.rb +1 -1
  61. data/lib/opennebula/hook_pool.rb +1 -1
  62. data/lib/opennebula/host.rb +1 -1
  63. data/lib/opennebula/host_pool.rb +1 -1
  64. data/lib/opennebula/image.rb +17 -14
  65. data/lib/opennebula/image_pool.rb +1 -1
  66. data/lib/opennebula/ldap_auth.rb +1 -1
  67. data/lib/opennebula/ldap_auth_spec.rb +1 -1
  68. data/lib/opennebula/lockable_ext.rb +163 -0
  69. data/lib/opennebula/marketplace.rb +1 -1
  70. data/lib/opennebula/marketplace_pool.rb +1 -1
  71. data/lib/opennebula/marketplaceapp.rb +9 -119
  72. data/lib/opennebula/marketplaceapp_ext.rb +522 -0
  73. data/lib/opennebula/marketplaceapp_pool.rb +1 -1
  74. data/lib/opennebula/oneflow_client.rb +4 -3
  75. data/lib/opennebula/pool.rb +4 -3
  76. data/lib/opennebula/pool_element.rb +1 -1
  77. data/lib/opennebula/security_group.rb +1 -1
  78. data/lib/opennebula/security_group_pool.rb +1 -1
  79. data/lib/opennebula/server_cipher_auth.rb +1 -1
  80. data/lib/opennebula/server_x509_auth.rb +1 -1
  81. data/lib/opennebula/ssh_auth.rb +1 -1
  82. data/lib/opennebula/system.rb +1 -1
  83. data/lib/opennebula/template.rb +4 -13
  84. data/lib/opennebula/template_ext.rb +325 -0
  85. data/lib/opennebula/template_pool.rb +1 -1
  86. data/lib/opennebula/user.rb +26 -2
  87. data/lib/opennebula/user_pool.rb +1 -1
  88. data/lib/opennebula/utils.rb +1 -1
  89. data/lib/opennebula/vdc.rb +1 -1
  90. data/lib/opennebula/vdc_pool.rb +1 -1
  91. data/lib/opennebula/virtual_machine.rb +25 -207
  92. data/lib/opennebula/virtual_machine_ext.rb +469 -0
  93. data/lib/opennebula/virtual_machine_pool.rb +1 -1
  94. data/lib/opennebula/virtual_network.rb +4 -10
  95. data/lib/opennebula/virtual_network_pool.rb +1 -1
  96. data/lib/opennebula/virtual_router.rb +4 -12
  97. data/lib/opennebula/virtual_router_pool.rb +1 -1
  98. data/lib/opennebula/vm_group.rb +4 -11
  99. data/lib/opennebula/vm_group_pool.rb +1 -1
  100. data/lib/opennebula/vntemplate.rb +4 -13
  101. data/lib/opennebula/vntemplate_pool.rb +1 -1
  102. data/lib/opennebula/wait_ext.rb +222 -0
  103. data/lib/opennebula/x509_auth.rb +1 -1
  104. data/lib/opennebula/xml_element.rb +1 -1
  105. data/lib/opennebula/xml_pool.rb +1 -1
  106. data/lib/opennebula/xml_utils.rb +1 -1
  107. data/lib/opennebula/zone.rb +1 -1
  108. data/lib/opennebula/zone_pool.rb +1 -1
  109. data/lib/rest_client.rb +201 -0
  110. data/lib/scripts_common.rb +180 -0
  111. data/lib/transport_zone.rb +43 -0
  112. data/lib/vcenter_driver.rb +13 -12
  113. data/lib/vcenter_importer.rb +616 -0
  114. data/lib/vi_client.rb +281 -0
  115. data/lib/vi_helper.rb +312 -0
  116. data/lib/virtual_machine.rb +3477 -0
  117. data/lib/virtual_wire.rb +158 -0
  118. data/lib/vm_device.rb +80 -0
  119. data/lib/vm_disk.rb +202 -0
  120. data/lib/vm_folder.rb +69 -0
  121. data/lib/vm_helper.rb +30 -0
  122. data/lib/vm_monitor.rb +303 -0
  123. data/lib/vm_nic.rb +70 -0
  124. data/lib/vm_template.rb +1961 -0
  125. data/lib/vmm_importer.rb +121 -0
  126. metadata +142 -29
@@ -0,0 +1,3477 @@
1
+ # -------------------------------------------------------------------------- #
2
+ # Copyright 2002-2021, OpenNebula Project, OpenNebula Systems #
3
+ # #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may #
5
+ # not use this file except in compliance with the License. You may obtain #
6
+ # a copy of the License at #
7
+ # #
8
+ # http://www.apache.org/licenses/LICENSE-2.0 #
9
+ # #
10
+ # Unless required by applicable law or agreed to in writing, software #
11
+ # distributed under the License is distributed on an "AS IS" BASIS, #
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
13
+ # See the License for the specific language governing permissions and #
14
+ # limitations under the License. #
15
+ #--------------------------------------------------------------------------- #
16
+ module VCenterDriver
17
+
18
+ ONE_LOCATION = ENV['ONE_LOCATION'] unless defined?(ONE_LOCATION)
19
+
20
+ if !ONE_LOCATION
21
+ unless defined?(RUBY_LIB_LOCATION)
22
+ RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
23
+ end
24
+ unless defined?(GEMS_LOCATION)
25
+ GEMS_LOCATION = '/usr/share/one/gems'
26
+ end
27
+ else
28
+ unless defined?(RUBY_LIB_LOCATION)
29
+ RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
30
+ end
31
+ unless defined?(GEMS_LOCATION)
32
+ GEMS_LOCATION = ONE_LOCATION + '/share/gems'
33
+ end
34
+ end
35
+
36
+ if File.directory?(GEMS_LOCATION)
37
+ real_gems_path = File.realpath(GEMS_LOCATION)
38
+ if !defined?(Gem) || Gem.path != [real_gems_path]
39
+ $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }
40
+ require 'rubygems'
41
+ Gem.use_paths(real_gems_path)
42
+ end
43
+ end
44
+
45
+ $LOAD_PATH << RUBY_LIB_LOCATION
46
+
47
+ require 'vm_device'
48
+ require 'vm_helper'
49
+ require 'vm_monitor'
50
+
51
+ ############################################################################
52
+ # Class VirtualMachine
53
+ ############################################################################
54
+ class VirtualMachine < VCenterDriver::Template
55
+
56
+ # Supported access to VirtualMachineDevice classes:
57
+ # Example:
58
+ # Disk
59
+ # VirtualMachineDevice::Disk
60
+ # VCenterDriver::VirtualMachine::Disk
61
+ include VirtualMachineDevice
62
+ include VirtualMachineHelper
63
+ include VirtualMachineMonitor
64
+
65
+ ########################################################################
66
+ # Virtual Machine main Class
67
+ ########################################################################
68
+
69
+ VM_PREFIX_DEFAULT = 'one-$i-'
70
+
71
+ POLL_ATTRIBUTE =
72
+ OpenNebula::VirtualMachine::Driver::POLL_ATTRIBUTE
73
+ VM_STATE =
74
+ OpenNebula::VirtualMachine::Driver::VM_STATE
75
+
76
+ DNET_CARD =
77
+ RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo
78
+ NET_CARD =
79
+ RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo
80
+ OPAQUE_CARD =
81
+ RbVmomi::VIM::VirtualEthernetCardOpaqueNetworkBackingInfo
82
+
83
+ VM_SHUTDOWN_TIMEOUT = 600 # 10 minutes til poweroff hard
84
+
85
+ attr_accessor :item, :vm_id
86
+
87
+ attr_accessor :vm_info
88
+
89
+ include Memoize
90
+
91
+ def initialize(vi_client, ref, one_id)
92
+ if ref
93
+ @item = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, ref)
94
+ check_item(@item, RbVmomi::VIM::VirtualMachine)
95
+ end
96
+
97
+ super(@item, vi_client)
98
+
99
+ @vi_client = vi_client
100
+ @vm_id = one_id
101
+ @locking = true
102
+ @vm_info = nil
103
+ @disks = {}
104
+ @nics = { :macs => {} }
105
+ end
106
+
107
+ ########################################################################
108
+ ########################################################################
109
+
110
+ # Attributes that must be defined when the VM does not exist in vCenter
111
+ attr_accessor :vi_client
112
+
113
+ # these have their own getter (if they aren't set, we can set them
114
+ # dynamically)
115
+ attr_writer :one_item
116
+ attr_writer :host
117
+ attr_writer :target_ds_ref
118
+
119
+ ########################################################################
120
+ ########################################################################
121
+
122
+ # The OpenNebula VM
123
+ # @return OpenNebula::VirtualMachine or XMLElement
124
+ def one_item
125
+ unless @one_item
126
+
127
+ if @vm_id == -1
128
+ raise 'VCenterDriver::Virtualmachine: '\
129
+ 'OpenNebula ID is mandatory for this vm!'
130
+ end
131
+
132
+ @one_item =
133
+ VIHelper
134
+ .one_item(
135
+ OpenNebula::VirtualMachine,
136
+ @vm_id
137
+ )
138
+ end
139
+
140
+ @one_item
141
+ end
142
+
143
+ # set the vmware item directly to the vm
144
+ def item_update(item)
145
+ @item = item
146
+ end
147
+
148
+ def disk_real_path(disk, disk_id)
149
+ volatile = disk['TYPE'] == 'fs'
150
+
151
+ if volatile
152
+ dir = disk['VCENTER_DS_VOLATILE_DIR'] || 'one-volatile'
153
+ img_path = "#{dir}/#{@vm_id}/one-#{@vm_id}-#{disk_id}.vmdk"
154
+ else
155
+ source = disk['SOURCE'].gsub('%20', ' ')
156
+ folder = File.dirname(source)
157
+ ext = File.extname(source)
158
+ file = File.basename(source, ext)
159
+
160
+ img_path = "#{folder}/#{file}-#{@vm_id}-#{disk_id}#{ext}"
161
+ end
162
+
163
+ img_path
164
+ end
165
+
166
+ # The OpenNebula host
167
+ # @return OpenNebula::Host or XMLElement
168
+ def host
169
+ if @host.nil?
170
+ if one_item.nil?
171
+ raise "'one_item' must be previously set to be able to " <<
172
+ 'access the OpenNebula host.'
173
+ end
174
+
175
+ host_id = one_item['HISTORY_RECORDS/HISTORY[last()]/HID']
176
+ raise 'No valid host_id found.' if host_id.nil?
177
+
178
+ @host = VIHelper.one_item(OpenNebula::Host, host_id)
179
+ end
180
+
181
+ @host
182
+ end
183
+
184
+ # Target Datastore VMware reference getter
185
+ # @return
186
+ def target_ds_ref
187
+ if @target_ds_ref.nil?
188
+ if one_item.nil?
189
+ raise "'one_item' must be previously set to be able to " <<
190
+ 'access the target Datastore.'
191
+ end
192
+
193
+ target_ds_id = one_item['HISTORY_RECORDS/HISTORY[last()]/DS_ID']
194
+ raise 'No valid target_ds_id found.' if target_ds_id.nil?
195
+
196
+ target_ds =
197
+ VCenterDriver::VIHelper
198
+ .one_item(
199
+ OpenNebula::Datastore,
200
+ target_ds_id
201
+ )
202
+
203
+ @target_ds_ref = target_ds['TEMPLATE/VCENTER_DS_REF']
204
+ end
205
+
206
+ @target_ds_ref
207
+ end
208
+
209
+ # Get a recommendation from a provided storagepod
210
+ # Returns the recommended datastore reference
211
+ def recommended_ds(ds_ref)
212
+ # Fail if datastore is not a storage pod
213
+ unless ds_ref.start_with?('group-')
214
+ raise 'Cannot recommend from a non storagepod reference'
215
+ end
216
+
217
+ # Retrieve information needed to create storage_spec hash
218
+ storage_manager =
219
+ vi_client
220
+ .vim
221
+ .serviceContent
222
+ .storageResourceManager
223
+ vcenter_name = vc_name
224
+ vc_template =
225
+ RbVmomi::VIM::VirtualMachine
226
+ .new(
227
+ vi_client.vim,
228
+ get_template_ref
229
+ )
230
+ dc = cluster.datacenter
231
+ vcenter_vm_folder_object = vcenter_folder(vcenter_folder_ref,
232
+ vc_template, dc)
233
+ storpod = get_ds(ds_ref)
234
+ disk_move_type = calculate_disk_move_type(storpod, vc_template,
235
+ linked_clones)
236
+ spec_hash = spec_hash_clone(disk_move_type)
237
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(spec_hash)
238
+
239
+ # Create hash needed to get the recommendation
240
+ storage_spec = RbVmomi::VIM.StoragePlacementSpec(
241
+ :type => 'clone',
242
+ :cloneName => vcenter_name,
243
+ :folder => vcenter_vm_folder_object,
244
+ :podSelectionSpec =>
245
+ RbVmomi::VIM
246
+ .StorageDrsPodSelectionSpec(
247
+ :storagePod => storpod
248
+ ),
249
+ :vm => vc_template,
250
+ :cloneSpec => clone_spec
251
+ )
252
+
253
+ # Query a storage placement recommendation
254
+ result = storage_manager
255
+ .RecommendDatastores(
256
+ :storageSpec => storage_spec
257
+ ) rescue nil
258
+ if result.nil?
259
+ raise 'Could not get placement specification for StoragePod'
260
+ end
261
+
262
+ if !result.respond_to?(:recommendations) ||
263
+ result.recommendations.empty?
264
+ raise 'Could not get placement specification for StoragePod'
265
+ end
266
+
267
+ # Return recommended DS reference
268
+ result.recommendations.first.action.first.destination._ref
269
+ end
270
+
271
+ # Cached cluster
272
+ # @return ClusterComputeResource
273
+ def cluster
274
+ if @cluster.nil?
275
+ ccr_ref = host['TEMPLATE/VCENTER_CCR_REF']
276
+ @cluster = ClusterComputeResource.new_from_ref(ccr_ref,
277
+ vi_client)
278
+ end
279
+
280
+ @cluster
281
+ end
282
+
283
+ ########################################################################
284
+ ########################################################################
285
+
286
+ # @return Boolean whether the VM exists in vCenter
287
+ def new?
288
+ one_item['DEPLOY_ID'].empty?
289
+ end
290
+
291
+ def wild?
292
+ !(one_item['TEMPLATE/IMPORTED'] &&
293
+ one_item['TEMPLATE/IMPORTED'] == 'YES').nil?
294
+ end
295
+
296
+ # @return Boolean wheter the vm exists in OpenNebula
297
+ def one_exist?
298
+ !@vm_id.nil? && @vm_id != -1
299
+ end
300
+
301
+ # @return String the vm_id stored in vCenter
302
+ def get_vm_id(vm_pool = nil)
303
+ if defined?(@vm_id) && @vm_id
304
+ return @vm_id
305
+ end
306
+
307
+ vm_ref = self['_ref']
308
+ return unless vm_ref
309
+
310
+ vc_uuid = vcenter_instance_uuid
311
+
312
+ one_vm =
313
+ VCenterDriver::VIHelper
314
+ .find_by_ref(
315
+ OpenNebula::VirtualMachinePool,
316
+ 'DEPLOY_ID',
317
+ vm_ref,
318
+ vc_uuid,
319
+ vm_pool
320
+ )
321
+ return unless one_vm
322
+
323
+ @vm_id = one_vm['ID']
324
+ @vm_id
325
+ end
326
+
327
+ def vcenter_instance_uuid
328
+ @vi_client.vim.serviceContent.about.instanceUuid
329
+ end
330
+
331
+ def disk_keys
332
+ unmanaged_keys = {}
333
+ @item.config.extraConfig.each do |val|
334
+ u = val[:key].include?('opennebula.disk')
335
+ m = val[:key].include?('opennebula.mdisk')
336
+ unmanaged_keys[val[:key]] = val[:value] if u || m
337
+ end
338
+
339
+ unmanaged_keys
340
+ end
341
+
342
+ ########################################################################
343
+ # Getters
344
+ ########################################################################
345
+
346
+ # @return RbVmomi::VIM::ResourcePool
347
+ def resource_pool
348
+ req_rp = one_item['VCENTER_RESOURCE_POOL'] ||
349
+ one_item['USER_TEMPLATE/VCENTER_RESOURCE_POOL']
350
+
351
+ # Get ref for req_rp
352
+ rp_list = cluster.get_resource_pool_list
353
+ req_rp_ref = rp_list.select do |rp|
354
+ rp[:name].downcase == req_rp.downcase
355
+ end.first[:ref] rescue nil
356
+
357
+ if vi_client.rp_confined?
358
+ if req_rp_ref && req_rp_ref != vi_client.rp._ref
359
+ raise 'Available resource pool '\
360
+ "[#{vi_client.rp.name}] in host"\
361
+ ' does not match requested resource pool'\
362
+ " [#{req_rp}]"
363
+ end
364
+
365
+ vi_client.rp
366
+ else
367
+ if req_rp_ref
368
+ rps = cluster.resource_pools.select do |r|
369
+ r._ref == req_rp_ref
370
+ end
371
+
372
+ if rps.empty?
373
+ raise "No matching resource pool found (#{req_rp})."
374
+ end
375
+
376
+ rps.first
377
+ else
378
+ cluster['resourcePool']
379
+ end
380
+ end
381
+ end
382
+
383
+ # @return RbVmomi::VIM::Datastore or nil
384
+ def get_ds(current_ds_ref = nil)
385
+ if !current_ds_ref
386
+ current_ds_id =
387
+ one_item[
388
+ 'HISTORY_RECORDS/HISTORY[last()]/DS_ID'
389
+ ]
390
+ current_ds = VCenterDriver::VIHelper.one_item(
391
+ OpenNebula::Datastore, current_ds_id
392
+ )
393
+ current_ds_ref = current_ds['TEMPLATE/VCENTER_DS_REF']
394
+ end
395
+
396
+ if current_ds_ref
397
+ dc = cluster.datacenter
398
+
399
+ ds_folder = dc.datastore_folder
400
+ ds = ds_folder.get(current_ds_ref)
401
+ ds.item rescue nil
402
+
403
+ else
404
+ nil
405
+ end
406
+ end
407
+
408
+ # StorageResouceManager reference
409
+ def storagemanager
410
+ self['_connection.serviceContent.storageResourceManager']
411
+ end
412
+
413
+ # @return Customization or nil
414
+ def customization_spec
415
+ xpath = 'USER_TEMPLATE/VCENTER_CUSTOMIZATION_SPEC'
416
+ customization_spec = one_item[xpath]
417
+
418
+ if customization_spec.nil?
419
+ return
420
+ end
421
+
422
+ begin
423
+ custom_spec = vi_client
424
+ .vim
425
+ .serviceContent
426
+ .customizationSpecManager
427
+ .GetCustomizationSpec(
428
+ :name => customization_spec
429
+ )
430
+
431
+ unless custom_spec && (spec = custom_spec.spec)
432
+ raise 'Error getting customization spec'
433
+ end
434
+
435
+ spec
436
+ rescue StandardError
437
+ raise "Customization spec '#{customization_spec}' not found"
438
+ end
439
+ end
440
+
441
+ # @return VCenterDriver::Datastore datastore
442
+ # where the disk will live under
443
+ def get_effective_ds(disk)
444
+ if disk['PERSISTENT'] == 'YES'
445
+ ds_ref = disk['VCENTER_DS_REF']
446
+ else
447
+ ds_ref = target_ds_ref
448
+
449
+ if ds_ref.nil?
450
+ raise 'target_ds_ref must be defined on this object.'
451
+ end
452
+ end
453
+
454
+ VCenterDriver::Storage.new_from_ref(ds_ref, vi_client)
455
+ end
456
+
457
+ # @return String vcenter name
458
+ def vc_name
459
+ vm_prefix = host['TEMPLATE/VM_PREFIX']
460
+ vm_prefix = VM_PREFIX_DEFAULT if vm_prefix.nil? || vm_prefix.empty?
461
+
462
+ if !one_item['USER_TEMPLATE/VM_PREFIX'].nil?
463
+ vm_prefix = one_item['USER_TEMPLATE/VM_PREFIX']
464
+ end
465
+ vm_prefix.gsub!('$i', one_item['ID'])
466
+
467
+ vm_suffix = ''
468
+ if !one_item['USER_TEMPLATE/VM_SUFFIX'].nil?
469
+ vm_suffix = one_item['USER_TEMPLATE/VM_SUFFIX']
470
+ end
471
+ vm_suffix.gsub!('$i', one_item['ID'])
472
+
473
+ vm_prefix + one_item['NAME'] + vm_suffix
474
+ end
475
+
476
+ # @return vCenter Tags
477
+ def vcenter_tags
478
+ one_item.info if one_item.instance_of?(OpenNebula::VirtualMachine)
479
+ one_item.retrieve_xmlelements('USER_TEMPLATE/VCENTER_TAG')
480
+ end
481
+
482
+ # @return if has vCenter Tags
483
+ def vcenter_tags?
484
+ !vcenter_tags.empty?
485
+ end
486
+
487
+ # @return if has cpuHotAddEnabled
488
+ def cpu_hot_add_enabled?
489
+ one_item.info if one_item.instance_of?(
490
+ OpenNebula::VirtualMachine
491
+ )
492
+
493
+ if one_item['USER_TEMPLATE/HOT_RESIZE/CPU_HOT_ADD_ENABLED'].nil?
494
+ return false
495
+ end
496
+
497
+ one_item[
498
+ 'USER_TEMPLATE/HOT_RESIZE/CPU_HOT_ADD_ENABLED'
499
+ ] == 'YES'
500
+ end
501
+
502
+ # @return if has memoryHotAddEnabled
503
+ def memory_hot_add_enabled?
504
+ one_item.info if one_item.instance_of?(
505
+ OpenNebula::VirtualMachine
506
+ )
507
+
508
+ if one_item['USER_TEMPLATE/HOT_RESIZE/MEMORY_HOT_ADD_ENABLED'].nil?
509
+ return false
510
+ end
511
+
512
+ one_item[
513
+ 'USER_TEMPLATE/HOT_RESIZE/MEMORY_HOT_ADD_ENABLED'
514
+ ] == 'YES'
515
+ end
516
+
517
+ ########################################################################
518
+ # Create and reconfigure VM related methods
519
+ ########################################################################
520
+
521
+ # This function permit get a folder by name if exist
522
+ # or create it if not exist
523
+ def find_or_create_folder(folder_root, name)
524
+ folder_root.childEntity.each do |child|
525
+ if child.instance_of?(RbVmomi::VIM::Folder) &&
526
+ child.name == name
527
+ return child
528
+ end
529
+ end
530
+
531
+ folder_root.CreateFolder(:name => name)
532
+ end
533
+
534
+ # This function creates a new VM from the
535
+ # driver_action XML and returns the
536
+ # VMware ref
537
+ # @param drv_action XML representing the deploy action
538
+ # @return String vmware ref
539
+ def clone_vm(drv_action)
540
+ vcenter_name = vc_name
541
+
542
+ dc = cluster.datacenter
543
+
544
+ vcenter_vm_folder = drv_action['USER_TEMPLATE/VCENTER_VM_FOLDER']
545
+
546
+ if !vcenter_vm_folder.nil? && !vcenter_vm_folder.empty?
547
+ vcenter_vm_folder =
548
+ vcenter_folder_name(vcenter_vm_folder, drv_action)
549
+
550
+ vcenter_vm_folder_object =
551
+ dc.item.find_folder(vcenter_vm_folder)
552
+
553
+ if vcenter_vm_folder_object.nil?
554
+ begin
555
+ dc.item.vmFolder.CreateFolder(
556
+ :name => vcenter_vm_folder
557
+ )
558
+ rescue StandardError => e
559
+ error_message = e.message
560
+ if VCenterDriver::CONFIG[:debug_information]
561
+ error_message += ' ' + e.backtrace
562
+ end
563
+ raise 'Cannot create Folder in vCenter:'\
564
+ "#{error_message}"
565
+ end
566
+ end
567
+ end
568
+
569
+ dc = cluster.datacenter
570
+
571
+ vcenter_vm_folder = drv_action['USER_TEMPLATE/VCENTER_VM_FOLDER']
572
+
573
+ if !vcenter_vm_folder.nil? && !vcenter_vm_folder.empty?
574
+ vcenter_vm_folder =
575
+ vcenter_folder_name(vcenter_vm_folder, drv_action)
576
+
577
+ vcenter_vm_folder_object =
578
+ dc.item.find_folder(vcenter_vm_folder)
579
+
580
+ if vcenter_vm_folder_object.nil?
581
+ begin
582
+ vcenter_vm_folder_list = vcenter_vm_folder.split('/')
583
+ folder_root = dc.item.vmFolder
584
+
585
+ vcenter_vm_folder_list.each do |folder_name|
586
+ folder_root = find_or_create_folder(
587
+ folder_root,
588
+ folder_name
589
+ )
590
+ end
591
+ rescue StandardError => e
592
+ error_message = e.message
593
+ if VCenterDriver::CONFIG[:debug_information]
594
+ error_message += ' ' + e.backtrace
595
+ end
596
+
597
+ raise 'Cannot create Folder in vCenter: '\
598
+ "#{error_message}"
599
+ end
600
+ end
601
+ end
602
+
603
+ vc_template_ref = drv_action['USER_TEMPLATE/VCENTER_TEMPLATE_REF']
604
+ vc_template = RbVmomi::VIM::VirtualMachine(@vi_client.vim,
605
+ vc_template_ref)
606
+
607
+ ds = get_ds
608
+
609
+ asking_for_linked_clones =
610
+ drv_action[
611
+ 'USER_TEMPLATE/VCENTER_LINKED_CLONES'
612
+ ]
613
+ disk_move_type = calculate_disk_move_type(ds,
614
+ vc_template,
615
+ asking_for_linked_clones)
616
+
617
+ spec_hash = spec_hash_clone(disk_move_type)
618
+
619
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(spec_hash)
620
+
621
+ vcenter_vm_folder_object =
622
+ vcenter_folder(
623
+ vcenter_vm_folder,
624
+ vc_template,
625
+ dc
626
+ )
627
+
628
+ if ds.instance_of? RbVmomi::VIM::StoragePod
629
+ # VM is cloned using Storage Resource Manager for StoragePods
630
+ begin
631
+ opts = {
632
+ :vc_template => vc_template,
633
+ :vcenter_name => vcenter_name,
634
+ :clone_spec => clone_spec,
635
+ :storpod => ds,
636
+ :vcenter_vm_folder_object => vcenter_vm_folder_object,
637
+ :dc => dc
638
+ }
639
+
640
+ vm = storagepod_clonevm_task(opts)
641
+ rescue StandardError => e
642
+ error =
643
+ "Cannot clone VM Template to StoragePod: #{e.message}."
644
+
645
+ if VCenterDriver::CONFIG[:debug_information]
646
+ error += "\n\n#{e.backtrace}"
647
+ end
648
+
649
+ raise error
650
+ end
651
+ else
652
+ vm = nil
653
+ begin
654
+ vm = vc_template.CloneVM_Task(
655
+ :folder => vcenter_vm_folder_object,
656
+ :name => vcenter_name,
657
+ :spec => clone_spec
658
+ ).wait_for_completion
659
+ rescue StandardError => e
660
+ if !e.message.start_with?('DuplicateName')
661
+ raise "Cannot clone VM Template: #{e.message}"
662
+ end
663
+
664
+ vm_folder = dc.vm_folder
665
+ vm_folder.fetch!
666
+ vm = vm_folder.items
667
+ .select {|_k, v| v.item.name == vcenter_name }
668
+ .values.first.item rescue nil
669
+
670
+ raise "Cannot clone VM Template: #{e.message}" unless vm
671
+
672
+ # Detach all persistent disks to
673
+ # avoid accidental destruction
674
+ detach_persistent_disks(vm)
675
+
676
+ vm.Destroy_Task.wait_for_completion
677
+ vm = vc_template.CloneVM_Task(
678
+ :folder => vcenter_vm_folder_object,
679
+ :name => vcenter_name,
680
+ :spec => clone_spec
681
+ ).wait_for_completion
682
+ end
683
+ end
684
+
685
+ # @item is populated
686
+ @item = vm
687
+
688
+ reference_unmanaged_devices(vc_template_ref)
689
+
690
+ self['_ref']
691
+ end
692
+
693
+ # This function clone a VM Template to StoragePod
694
+ # @param opts HASH with all parameters need it to clone
695
+ # opts = {
696
+ # :vc_template => vc_template,
697
+ # :vcenter_name => vcenter_name,
698
+ # :clone_spec => clone_spec,
699
+ # :storpod => ds,
700
+ # :vcenter_vm_folder_object => vcenter_vm_folder_object,
701
+ # :dc => dc
702
+ # }
703
+ # @return vm (VirtualMachine)
704
+ def storagepod_clonevm_task(opts)
705
+ vc_template = opts[:vc_template]
706
+ vcenter_name = opts[:vcenter_name]
707
+ clone_spec = opts[:clone_spec]
708
+ storpod = opts[:storpod]
709
+ vcenter_vm_folder_object = opts[:vcenter_vm_folder_object]
710
+ dc = opts[:dc]
711
+
712
+ storage_manager =
713
+ vc_template
714
+ ._connection
715
+ .serviceContent
716
+ .storageResourceManager
717
+
718
+ storage_spec = RbVmomi::VIM.StoragePlacementSpec(
719
+ :type => 'clone',
720
+ :cloneName => vcenter_name,
721
+ :folder => vcenter_vm_folder_object,
722
+ :podSelectionSpec =>
723
+ RbVmomi::VIM
724
+ .StorageDrsPodSelectionSpec(
725
+ :storagePod => storpod
726
+ ),
727
+ :vm => vc_template,
728
+ :cloneSpec => clone_spec
729
+ )
730
+
731
+ # Query a storage placement recommendation
732
+ result = storage_manager
733
+ .RecommendDatastores(
734
+ :storageSpec => storage_spec
735
+ ) rescue nil
736
+
737
+ if result.nil?
738
+ raise 'Could not get placement specification for StoragePod'
739
+ end
740
+
741
+ if !result
742
+ .respond_to?(
743
+ :recommendations
744
+ ) || result.recommendations.empty?
745
+ raise 'Could not get placement specification for StoragePod'
746
+ end
747
+
748
+ # Get recommendation key to be applied
749
+ key = result.recommendations.first.key ||= ''
750
+ if key.empty?
751
+ raise 'Missing Datastore recommendation for StoragePod'
752
+ end
753
+
754
+ begin
755
+ apply_sr = storage_manager
756
+ .ApplyStorageDrsRecommendation_Task(:key => [key])
757
+ .wait_for_completion
758
+ apply_sr.vm
759
+ rescue StandardError => e
760
+ if !e.message.start_with?('DuplicateName')
761
+ raise 'Cannot clone VM Template: '\
762
+ "#{e.message}\n#{e.backtrace}"
763
+ end
764
+
765
+ # The VM already exists, try to find the vm
766
+ vm_folder = dc.vm_folder
767
+ vm_folder.fetch!
768
+ vm = vm_folder.items
769
+ .select {|_k, v| v.item.name == vcenter_name }
770
+ .values.first.item rescue nil
771
+
772
+ if vm
773
+
774
+ begin
775
+ # Detach all persistent disks to
776
+ # avoid accidental destruction
777
+ detach_persistent_disks(vm)
778
+
779
+ # Destroy the VM with any disks still attached to it
780
+ vm.Destroy_Task.wait_for_completion
781
+
782
+ # Query a storage placement recommendation
783
+ result =
784
+ storage_manager
785
+ .RecommendDatastores(
786
+ :storageSpec => storage_spec
787
+ ) rescue nil
788
+
789
+ if result.nil?
790
+ raise 'Could not get placement specification '\
791
+ 'for StoragePod'
792
+ end
793
+
794
+ if !result
795
+ .respond_to?(
796
+ :recommendations
797
+ ) ||
798
+ result
799
+ .recommendations.empty?
800
+ raise 'Could not get placement '\
801
+ 'specification for StoragePod'
802
+ end
803
+
804
+ # Get recommendation key to be applied
805
+ key = result.recommendations.first.key ||= ''
806
+ if key.empty?
807
+ raise 'Missing Datastore recommendation '\
808
+ ' for StoragePod'
809
+ end
810
+
811
+ apply_sr =
812
+ storage_manager
813
+ .ApplyStorageDrsRecommendation_Task(
814
+ :key => [key]
815
+ )
816
+ .wait_for_completion
817
+ apply_sr.vm
818
+ rescue StandardError => e
819
+ raise 'Failure applying recommendation while '\
820
+ "cloning VM: #{e.message}"
821
+ end
822
+ end
823
+ end
824
+ end
825
+
826
+ # Calculates how to move disk backinggs from the
827
+ # vCenter VM Template moref
828
+ def calculate_disk_move_type(ds, vc_template, use_linked_clones)
829
+ # Default disk move type (Full Clone)
830
+ disk_move_type = :moveAllDiskBackingsAndDisallowSharing
831
+
832
+ if ds.instance_of?(RbVmomi::VIM::Datastore) &&
833
+ use_linked_clones &&
834
+ use_linked_clones.downcase == 'yes'
835
+
836
+ # Check if all disks in template has delta disks
837
+ disks = vc_template.config
838
+ .hardware
839
+ .device
840
+ .grep(RbVmomi::VIM::VirtualDisk)
841
+
842
+ disks_no_delta = disks.select do |d|
843
+ d.backing.parent.nil?
844
+ end
845
+
846
+ # Can use linked clones if all disks have delta disks
847
+ if disks_no_delta.empty?
848
+ disk_move_type = :moveChildMostDiskBacking
849
+ end
850
+ end
851
+
852
+ disk_move_type
853
+ end
854
+
855
+ # @return String vcenter folder name
856
+ def vcenter_folder_name(vm_folder_name, drv_action)
857
+ uname = drv_action['UNAME']
858
+ gname = drv_action['GNAME']
859
+
860
+ vm_folder_name.gsub!('$uname', uname)
861
+ vm_folder_name.gsub!('$gname', gname)
862
+
863
+ vm_folder_name
864
+ end
865
+
866
+ # Get vcenter folder object from the reference
867
+ # If folder is not found, the folder of the
868
+ # vCenter VM Template is returned
869
+ def vcenter_folder(vcenter_vm_folder, vc_template, dc)
870
+ vcenter_vm_folder_object = nil
871
+
872
+ if !vcenter_vm_folder.nil? && !vcenter_vm_folder.empty?
873
+ vcenter_vm_folder_object =
874
+ dc
875
+ .item
876
+ .find_folder(
877
+ vcenter_vm_folder
878
+ )
879
+ end
880
+
881
+ vcenter_vm_folder_object =
882
+ vc_template
883
+ .parent if vcenter_vm_folder_object.nil?
884
+ vcenter_vm_folder_object
885
+ end
886
+
887
+ # @return clone parameters spec hash
888
+ def spec_hash_clone(disk_move_type)
889
+ # Relocate spec
890
+ relocate_spec_params = {}
891
+
892
+ relocate_spec_params[:pool] = resource_pool
893
+ relocate_spec_params[:diskMoveType] = disk_move_type
894
+
895
+ ds = get_ds
896
+
897
+ relocate_spec_params[:datastore] =
898
+ ds if ds.instance_of? RbVmomi::VIM::Datastore
899
+
900
+ relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(
901
+ relocate_spec_params
902
+ )
903
+
904
+ # Running flag - prevents spurious poweroff states in the VM
905
+ running_flag = [{ :key => 'opennebula.vm.running', :value => 'no' }]
906
+
907
+ running_flag_spec = RbVmomi::VIM.VirtualMachineConfigSpec(
908
+ { :extraConfig => running_flag }
909
+ )
910
+
911
+ clone_parameters = {
912
+ :location => relocate_spec,
913
+ :powerOn => false,
914
+ :template => false,
915
+ :config => running_flag_spec
916
+ }
917
+
918
+ cs = customization_spec
919
+ clone_parameters[:customization] = cs if cs
920
+
921
+ clone_parameters
922
+ end
923
+
924
+ ########################################################################
925
+ # VirtualMachine Resource model methods
926
+ ########################################################################
927
+
928
+ #
929
+ # gets the representation of the nics
930
+ #
931
+ # @return [Hash(String => self.Nic)
932
+ def nics
933
+ if !@nics[:macs].empty?
934
+ return @nics.reject {|k| k == :macs }
935
+ end
936
+
937
+ info_nics
938
+ end
939
+
940
+ # gets the representation of the disks
941
+ #
942
+ # @return [Hash(String => self.Disk)
943
+ def disks
944
+ return @disks unless @disks.empty?
945
+
946
+ info_disks
947
+ end
948
+
949
+ # iterate over the nics model
950
+ #
951
+ # @param condition[Symbol] selects nics that matches certain condition
952
+ # see Self.Nic|Resource class to see some methods: :exits?, :one?...
953
+ #
954
+ # @return yield the nic
955
+ def nics_each(condition)
956
+ res = []
957
+ nics.each do |_id, nic|
958
+ next unless nic.method(condition).call
959
+
960
+ yield nic if block_given?
961
+
962
+ res << nic
963
+ end
964
+
965
+ res
966
+ end
967
+
968
+ # iterate over the disks model
969
+ #
970
+ # @param condition[Symbol] selects disks that matches certain condition
971
+ # see Self.Disk|Resource class to see some methods: :exits?, :one?...
972
+ #
973
+ # @return yield the disk
974
+ def disks_each(condition)
975
+ res = []
976
+ disks.each do |_id, disk|
977
+ next unless disk.method(condition).call
978
+
979
+ yield disk if block_given?
980
+
981
+ res << disk
982
+ end
983
+
984
+ res
985
+ end
986
+
987
+ def disks_synced?
988
+ disks_each(:unsynced?) { return false }
989
+
990
+ true
991
+ end
992
+
993
+ def template_ref_get
994
+ one_item['USER_TEMPLATE/VCENTER_TEMPLATE_REF']
995
+ end
996
+
997
+ def vcenter_folder_ref
998
+ one_item['USER_TEMPLATE/VCENTER_VM_FOLDER']
999
+ end
1000
+
1001
+ # Queries to OpenNebula the machine disks xml representation
1002
+ def one_disks_list
1003
+ one_item.info if one_item.instance_of?(OpenNebula::VirtualMachine)
1004
+ one_item.retrieve_xmlelements('TEMPLATE/DISK')
1005
+ end
1006
+
1007
+ # Queries to OpenNebula the machine nics xml representation
1008
+ def one_nics_get
1009
+ one_item.info if one_item.instance_of?(OpenNebula::VirtualMachine)
1010
+ one_item.retrieve_xmlelements('TEMPLATE/NIC')
1011
+ end
1012
+
1013
+ def linked_clones
1014
+ one_item['USER_TEMPLATE/VCENTER_LINKED_CLONES']
1015
+ end
1016
+
1017
+ # perform a query to vCenter asking for the OpenNebula disk
1018
+ #
1019
+ # @param one_disk [XMLelement] The OpenNebula object
1020
+ # representation of the disk
1021
+ # @param keys [Hash (String => String)] Hashmap with
1022
+ # the unmanaged keys
1023
+ # @param vc_disks [Array (vcenter_disks)] Array of
1024
+ # the machine real disks
1025
+ # See vcenter_disks_get method
1026
+ #
1027
+ # @return [vCenter_disk] the proper disk
1028
+ def query_disk(one_disk, keys, vc_disks)
1029
+ index = one_disk['DISK_ID']
1030
+ unmanaged = "opennebula.disk.#{index}"
1031
+ managed = "opennebula.mdisk.#{index}"
1032
+
1033
+ if keys[managed]
1034
+ key = keys[managed].to_i
1035
+ elsif keys[unmanaged]
1036
+ key = keys[unmanaged].to_i
1037
+ end
1038
+
1039
+ if key
1040
+ query = vc_disks.select {|dev| key == dev[:key] }
1041
+ else
1042
+ if snapshots?
1043
+ error = 'Disk metadata not present and snapshots exist. ' \
1044
+ 'OpenNebula cannot manage this VM.'
1045
+ raise error
1046
+ end
1047
+
1048
+ # Try to find the disk using the path known by OpenNebula
1049
+ source_path = one_disk['SOURCE']
1050
+ calculated_path = disk_real_path(one_disk, index)
1051
+ query = vc_disks.select do |dev|
1052
+ source_path == dev[:path_wo_ds] ||
1053
+ calculated_path == dev[:path_wo_ds]
1054
+ end
1055
+ end
1056
+
1057
+ return if query.size != 1
1058
+
1059
+ query.first
1060
+ end
1061
+
1062
+ # perform a query to vCenter asking for the OpenNebula nic
1063
+ #
1064
+ # @param vc_disks [String] The mac of the nic
1065
+ # @param vc_disks [Array (vcenter_nic)] Array of the machine real nics
1066
+ #
1067
+ # @return [vCenter_nic] the proper nic
1068
+ def query_nic(mac, vc_nics)
1069
+ nic = vc_nics.select {|dev| dev.macAddress == mac }.first
1070
+
1071
+ vc_nics.delete(nic) if nic
1072
+ end
1073
+
1074
+ # Refresh VcenterDriver machine nics model, does not perform
1075
+ # any sync operation!
1076
+ #
1077
+ # @return [Hash ("String" => self.Nic)] Model representation of nics
1078
+ def info_nics
1079
+ @nics = { :macs => {} }
1080
+
1081
+ vc_nics = vcenter_nics_list
1082
+ one_nics = one_nics_get
1083
+
1084
+ one_nics.each do |one_nic|
1085
+ index = one_nic['NIC_ID']
1086
+ mac = one_nic['MAC']
1087
+ vc_dev = query_nic(mac, vc_nics)
1088
+
1089
+ if vc_dev
1090
+ @nics[index] = Nic.new(index.to_i, one_nic, vc_dev)
1091
+ @nics[:macs][mac] = index
1092
+ else
1093
+ @nics[index] = Nic.one_nic(index.to_i, one_nic)
1094
+ end
1095
+ end
1096
+
1097
+ vc_nics.each do |d|
1098
+ backing = d.backing
1099
+
1100
+ case backing.class.to_s
1101
+ when NET_CARD.to_s
1102
+ key = backing.network._ref
1103
+ when DNET_CARD.to_s
1104
+ key = backing.port.portgroupKey
1105
+ when OPAQUE_CARD.to_s
1106
+ # Select only Opaque Networks
1107
+ opaque_networks = @item.network.select do |net|
1108
+ RbVmomi::VIM::OpaqueNetwork == net.class
1109
+ end
1110
+ opaque_network = opaque_networks.find do |opn|
1111
+ backing.opaqueNetworkId == opn.summary.opaqueNetworkId
1112
+ end
1113
+ key = opaque_network._ref
1114
+ else
1115
+ raise "Unsupported network card type: #{backing.class}"
1116
+ end
1117
+
1118
+ @nics["#{key}#{d.key}"] = Nic.vc_nic(d)
1119
+ end
1120
+
1121
+ @nics.reject {|k| k == :macs }
1122
+ end
1123
+
1124
+ # Refresh VcenterDriver machine disks model, does not perform any
1125
+ # sync operation!
1126
+ #
1127
+ # @return [Hash ("String" => self.Disk)] Model representation of disks
1128
+ def info_disks
1129
+ @disks = {}
1130
+
1131
+ keys = disk_keys
1132
+ vc_disks = vcenter_disks_get
1133
+ one_disks = one_disks_list
1134
+
1135
+ one_disks.each do |one_disk|
1136
+ index = one_disk['DISK_ID']
1137
+
1138
+ disk = query_disk(one_disk, keys, vc_disks)
1139
+
1140
+ vc_dev = vc_disks.delete(disk) if disk
1141
+
1142
+ if vc_dev
1143
+ @disks[index] = Disk.new(index.to_i, one_disk, vc_dev)
1144
+ else
1145
+ @disks[index] = Disk.one_disk(index.to_i, one_disk)
1146
+ end
1147
+ end
1148
+
1149
+ vc_disks.each {|d| @disks[d[:path_wo_ds]] = Disk.vc_disk(d) }
1150
+
1151
+ @disks
1152
+ end
1153
+
1154
+ # Queries for a certain nic
1155
+ #
1156
+ # @param index [String| Integer] the id of the nic or the mac
1157
+ # @param opts [hash (symbol=>boolean)]
1158
+ # :sync : allow you to ignore local class memory
1159
+ def nic(index, opts = {})
1160
+ index = index.to_s
1161
+ is_mac = index.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)
1162
+
1163
+ if is_mac
1164
+ mac = index
1165
+ index = @nics[:macs][mac]
1166
+ end
1167
+
1168
+ return @nics[index] if @nics[index] && opts[:sync].nil?
1169
+
1170
+ if is_mac
1171
+ one_nic =
1172
+ one_item
1173
+ .retrieve_xmlelements(
1174
+ "TEMPLATE/NIC[MAC='#{mac}']"
1175
+ ).first rescue nil
1176
+ index = one_nic['NIC_ID'] if one_nic
1177
+ else
1178
+ one_nic =
1179
+ one_item
1180
+ .retrieve_xmlelements(
1181
+ "TEMPLATE/NIC[NIC_ID='#{index}']"
1182
+ ).first rescue nil
1183
+ mac = one_nic['MAC'] if one_nic
1184
+ end
1185
+
1186
+ raise "nic #{index} not found" unless one_nic
1187
+
1188
+ vc_nics = vcenter_nics_list
1189
+ vc_nic = query_nic(mac, vc_nics)
1190
+
1191
+ if vc_nic
1192
+ Nic.new(index.to_i, one_nic, vc_nic)
1193
+ else
1194
+ Nic.one_nic(index.to_i, one_nic)
1195
+ end
1196
+ end
1197
+
1198
+ # Queries for a certain disk
1199
+ #
1200
+ # @param index [String | Integer] the id of the disk
1201
+ # @param opts [hash (symbol=>boolean)]
1202
+ # :sync : allow you to ignore local class memory
1203
+ def disk(index, opts = {})
1204
+ index = index.to_s
1205
+
1206
+ return @disks[index] if @disks[index] && opts[:sync].nil?
1207
+
1208
+ one_disk =
1209
+ one_item
1210
+ .retrieve_xmlelements(
1211
+ "TEMPLATE/DISK[DISK_ID='#{index}']"
1212
+ ).first rescue nil
1213
+
1214
+ raise "disk #{index} not found" unless one_disk
1215
+
1216
+ opts[:keys].nil? ? keys = disk_keys : keys = opts[:keys]
1217
+ if opts[:disks].nil?
1218
+ vc_disks = vcenter_disks_get
1219
+ else
1220
+ vc_disks = opts[:disks]
1221
+ end
1222
+ vc_disk = query_disk(one_disk, keys, vc_disks)
1223
+
1224
+ if vc_disk
1225
+ Disk.new(index.to_i, one_disk, vc_disk)
1226
+ else
1227
+ Disk.one_disk(index.to_i, one_disk)
1228
+ end
1229
+ end
1230
+
1231
+ # Matches disks from the vCenter VM Template (or VM if it is coming
1232
+ # from a Wild VM) with the disks represented in OpenNebula VM
1233
+ # data model (ie, the XML)
1234
+ def reference_unmanaged_devices(template_ref, execute = true)
1235
+ device_change = []
1236
+ spec = {}
1237
+
1238
+ # Get unmanaged disks in OpenNebula's VM template
1239
+ xpath =
1240
+ 'TEMPLATE/DISK[OPENNEBULA_MANAGED="NO" '\
1241
+ 'or OPENNEBULA_MANAGED="no"]'
1242
+ unmanaged_disks = one_item.retrieve_xmlelements(xpath)
1243
+
1244
+ managed = false
1245
+ extraconfig = reference_disks(
1246
+ template_ref,
1247
+ unmanaged_disks,
1248
+ managed
1249
+ )
1250
+
1251
+ # Add info for existing nics in template in vm xml
1252
+ xpath =
1253
+ 'TEMPLATE/NIC[OPENNEBULA_MANAGED="NO" '\
1254
+ 'or OPENNEBULA_MANAGED="no"]'
1255
+ unmanaged_nics = one_item.retrieve_xmlelements(xpath)
1256
+
1257
+ # Handle NIC changes (different model and/or set MAC address
1258
+ # for unmanaged nics
1259
+ begin
1260
+ if !unmanaged_nics.empty?
1261
+ nics = vcenter_nics_list
1262
+
1263
+ # iterate over nics array and find nic with ref
1264
+ # or return nil if not exist
1265
+ select_net =lambda {|ref|
1266
+ device = nil
1267
+ nics.each do |nic|
1268
+ type = nic.backing.class.to_s
1269
+
1270
+ case type
1271
+ when NET_CARD.to_s
1272
+ nref = nic.backing.network._ref
1273
+ when DNET_CARD.to_s
1274
+ nref = nic.backing.port.portgroupKey
1275
+ when OPAQUE_CARD.to_s
1276
+ # Select only Opaque Networks
1277
+ opaque_networks = @item.network.select do |net|
1278
+ RbVmomi::VIM::OpaqueNetwork == net.class
1279
+ end
1280
+ opaque_network = opaque_networks.find do |opn|
1281
+ nic.backing.opaqueNetworkId ==
1282
+ opn.summary.opaqueNetworkId
1283
+ end
1284
+ nref = opaque_network._ref
1285
+ else
1286
+ raise 'Unsupported network card type: '\
1287
+ "#{nic.backing.class}"
1288
+ end
1289
+
1290
+ next unless nref == ref
1291
+
1292
+ device = nic
1293
+ break
1294
+ end
1295
+
1296
+ if device
1297
+ nics.delete(device)
1298
+ else
1299
+ nil
1300
+ end
1301
+ }
1302
+
1303
+ # Go over all unmanaged nics in order to sync
1304
+ # with vCenter Virtual Machine
1305
+ unmanaged_nics.each do |unic|
1306
+ vnic = select_net.call(unic['VCENTER_NET_REF'])
1307
+ nic_class = vnic.class if vnic
1308
+
1309
+ if unic['MODEL']
1310
+ new_model = Nic.nic_model_class(unic['MODEL'])
1311
+ end
1312
+
1313
+ # if vnic is nil add a new device
1314
+ if vnic.nil?
1315
+ device_change << calculate_add_nic_spec(unic)
1316
+ # delete actual nic and update the new one.
1317
+ elsif new_model && new_model != nic_class
1318
+ device_change << {
1319
+ :device => vnic,
1320
+ :operation => :remove
1321
+ }
1322
+ device_change << calculate_add_nic_spec(
1323
+ unic,
1324
+ vnic.unitNumber
1325
+ )
1326
+ else
1327
+ vnic.macAddress = unic['MAC']
1328
+ device_change << {
1329
+ :device => vnic,
1330
+ :operation => :edit
1331
+ }
1332
+ end
1333
+ end
1334
+
1335
+ end
1336
+ rescue StandardError => e
1337
+ raise 'There is a problem with your vm NICS, '\
1338
+ 'make sure that they are working properly. '\
1339
+ "Error: #{e.message}"
1340
+ end
1341
+
1342
+ # Save in extraconfig the key for unmanaged disks
1343
+ if !extraconfig.empty? || !device_change.empty?
1344
+ spec[:extraConfig] = extraconfig unless extraconfig.empty?
1345
+ spec[:deviceChange] = device_change unless device_change.empty?
1346
+
1347
+ return spec unless execute
1348
+
1349
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
1350
+ end
1351
+
1352
+ {}
1353
+ end
1354
+
1355
+ def reference_all_disks
1356
+ # OpenNebula VM disks saved inside .vmx file in vCenter
1357
+ disks_extraconfig_current = {}
1358
+ # iterate over all attributes and get the disk information
1359
+ # keys for disks are prefixed with
1360
+ # opennebula.disk and opennebula.mdisk
1361
+ @item.config.extraConfig.each do |elem|
1362
+ disks_extraconfig_current[elem.key] =
1363
+ elem.value if elem.key.start_with?('opennebula.disk.')
1364
+ disks_extraconfig_current[elem.key] =
1365
+ elem.value if elem.key.start_with?('opennebula.mdisk.')
1366
+ end
1367
+
1368
+ # disks that exist currently in the vCenter Virtual Machine
1369
+ disks_vcenter_current = []
1370
+ disks_each(:synced?) do |disk|
1371
+ begin
1372
+ if disk.managed?
1373
+ key_prefix = 'opennebula.mdisk.'
1374
+ else
1375
+ key_prefix = 'opennebula.disk.'
1376
+ end
1377
+ k = "#{key_prefix}#{disk.id}"
1378
+ v = disk.key.to_s
1379
+
1380
+ disks_vcenter_current << { :key => k, :value => v }
1381
+ rescue StandardError => _e
1382
+ next
1383
+ end
1384
+ end
1385
+
1386
+ update = false
1387
+ # differences in the number of disks
1388
+ # between vCenter and OpenNebula VMs
1389
+ num_disks_difference =
1390
+ disks_extraconfig_current.keys.count -
1391
+ disks_vcenter_current.count
1392
+
1393
+ # check if disks are same in vCenter and OpenNebula
1394
+ disks_vcenter_current.each do |item|
1395
+ # check if vCenter disk have representation in the extraConfig
1396
+ # but with a different key, then we have to update
1397
+ first_condition =
1398
+ disks_extraconfig_current.key? item[:key]
1399
+ second_condition =
1400
+ disks_extraconfig_current[item[:key]] == item[:value]
1401
+ if first_condition && !second_condition
1402
+ update = true
1403
+ end
1404
+ # check if vCenter disk hasn't got
1405
+ # a representation in the extraConfig
1406
+ # then we have to update
1407
+ if !disks_extraconfig_current.key? item[:key]
1408
+ update = true
1409
+ end
1410
+ end
1411
+
1412
+ # new configuration for vCenter .vmx file
1413
+ disks_extraconfig_new = {}
1414
+
1415
+ return unless num_disks_difference != 0 || update
1416
+
1417
+ # Step 1: remove disks in the current configuration of .vmx
1418
+ # Avoids having an old disk in the configuration
1419
+ # that does not really exist
1420
+ disks_extraconfig_current.keys.each do |key|
1421
+ disks_extraconfig_new[key] = ''
1422
+ end
1423
+
1424
+ # Step 2: add current vCenter disks to new configuration
1425
+ disks_vcenter_current.each do |item|
1426
+ disks_extraconfig_new[item[:key]] = item[:value]
1427
+ end
1428
+
1429
+ # Step 3: create extraconfig_new with the values to update
1430
+ extraconfig_new = []
1431
+ disks_extraconfig_new.keys.each do |key|
1432
+ extraconfig_new <<
1433
+ {
1434
+ :key =>
1435
+ key,
1436
+ :value =>
1437
+ disks_extraconfig_new[key]
1438
+ }
1439
+ end
1440
+
1441
+ # Step 4: update the extraConfig
1442
+ spec_hash = { :extraConfig => extraconfig_new }
1443
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
1444
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
1445
+ end
1446
+
1447
+ # Build extraconfig section to reference disks
1448
+ # by key and avoid problems with changing paths
1449
+ # (mainly due to snapshots)
1450
+ # Uses VM Templte if ref available, or the vCenter VM if not
1451
+ # (latter case is if we are dealing with a Wild VM
1452
+ def reference_disks(template_ref, disks, managed)
1453
+ return [] if disks.empty? || instantiated_as_persistent?
1454
+
1455
+ extraconfig = []
1456
+ if managed
1457
+ key_prefix = 'opennebula.mdisk'
1458
+ else
1459
+ key_prefix = 'opennebula.disk'
1460
+ end
1461
+
1462
+ # Get vcenter VM disks to know real path of cloned disk
1463
+ vcenter_disks = vcenter_disks_get
1464
+
1465
+ # Create an array with the paths of the disks in vcenter template
1466
+ if !template_ref.nil?
1467
+ template = VCenterDriver::Template.new_from_ref(template_ref,
1468
+ vi_client)
1469
+ template_disks = template.vcenter_disks_get
1470
+ else
1471
+ # If we are dealing with a Wild VM, we simply use
1472
+ # what is available in the vCenter VM
1473
+ template_disks = vcenter_disks_get
1474
+ end
1475
+ template_disks_vector = []
1476
+ template_disks.each do |d|
1477
+ template_disks_vector << d[:path_wo_ds]
1478
+ end
1479
+
1480
+ # Try to find index of disks in template disks
1481
+ disks.each do |disk|
1482
+ disk_source =
1483
+ VCenterDriver::FileHelper
1484
+ .unescape_path(
1485
+ disk['SOURCE']
1486
+ )
1487
+ template_disk = template_disks.select do |d|
1488
+ d[:path_wo_ds] == disk_source
1489
+ end.first
1490
+
1491
+ if template_disk
1492
+ vcenter_disk = vcenter_disks.select do |d|
1493
+ d[:key] == template_disk[:key]
1494
+ end.first
1495
+ end
1496
+
1497
+ unless vcenter_disk
1498
+ raise "disk with path #{disk_source}"\
1499
+ 'not found in the vCenter VM'
1500
+ end
1501
+
1502
+ reference = {}
1503
+ reference[:key] = "#{key_prefix}.#{disk['DISK_ID']}"
1504
+ reference[:value] = (vcenter_disk[:key]).to_s
1505
+ extraconfig << reference
1506
+ end
1507
+
1508
+ extraconfig
1509
+ end
1510
+
1511
+ # create storagedrs disks
1512
+ #
1513
+ # @param device_change_spod [array] add disk spec for every device
1514
+ #
1515
+ # @param device_change_spod_ids [object] map from unit ctrl to
1516
+ # disk_id
1517
+ #
1518
+ # @return extra_config [Array] array with the extra config for vCenter
1519
+ def create_storagedrs_disks(device_change_spod, device_change_spod_ids)
1520
+ sm = storagemanager
1521
+ disk_locator = []
1522
+ extra_config = []
1523
+
1524
+ device_change_spod.each do |device_spec|
1525
+ disk_locator <<
1526
+ RbVmomi::VIM
1527
+ .PodDiskLocator(
1528
+ :diskId => device_spec[
1529
+ :device
1530
+ ].key
1531
+ )
1532
+ end
1533
+
1534
+ spec = {}
1535
+ spec[:deviceChange] = device_change_spod
1536
+
1537
+ # Disk locator is required for AddDisk
1538
+ vmpod_hash = {}
1539
+ vmpod_hash[:storagePod] = get_ds
1540
+ vmpod_hash[:disk] = disk_locator
1541
+ vmpod_config = RbVmomi::VIM::VmPodConfigForPlacement(vmpod_hash)
1542
+
1543
+ # The storage pod selection requires initialize
1544
+ spod_hash = {}
1545
+ spod_hash[:initialVmConfig] = [vmpod_config]
1546
+ spod_select = RbVmomi::VIM::StorageDrsPodSelectionSpec(spod_hash)
1547
+ storage_spec = RbVmomi::VIM.StoragePlacementSpec(
1548
+ :type => :reconfigure,
1549
+ :podSelectionSpec => spod_select,
1550
+ :vm => self['_ref'],
1551
+ :configSpec => spec
1552
+ )
1553
+
1554
+ # Query a storage placement recommendation
1555
+ result = sm
1556
+ .RecommendDatastores(
1557
+ :storageSpec => storage_spec
1558
+ ) rescue nil
1559
+
1560
+ if result.nil?
1561
+ raise 'Could not get placement specification for StoragePod'
1562
+ end
1563
+
1564
+ if !result.respond_to?(:recommendations) ||
1565
+ result.recommendations.empty?
1566
+ raise 'Could not get placement specification for StoragePod'
1567
+ end
1568
+
1569
+ # Get recommendation key to be applied
1570
+ key = result.recommendations.first.key ||= ''
1571
+
1572
+ if key.empty?
1573
+ raise 'Missing Datastore recommendation for StoragePod'
1574
+ end
1575
+
1576
+ # Apply recommendation
1577
+ sm.ApplyStorageDrsRecommendation_Task(
1578
+ :key => [key]
1579
+ ).wait_for_completion
1580
+
1581
+ # Set references in opennebula.disk elements
1582
+ device_change_spod.each do |device_spec|
1583
+ unit_number = device_spec[:device].unitNumber
1584
+ controller_key = device_spec[:device].controllerKey
1585
+ key = get_vcenter_disk_key(unit_number,
1586
+ controller_key)
1587
+ disk_id =
1588
+ device_change_spod_ids[
1589
+ "#{controller_key}-#{unit_number}"
1590
+ ]
1591
+ reference = {}
1592
+ reference[:key] = "opennebula.disk.#{disk_id}"
1593
+ reference[:value] = key.to_s
1594
+ extra_config << reference
1595
+ end
1596
+
1597
+ extra_config
1598
+ end
1599
+
1600
+ # set the boot order of the machine
1601
+ #
1602
+ # @param boot_info [String] boot information stored in
1603
+ # the template of the virtual machine. example: disk0, nic0
1604
+ #
1605
+ # @return [Array (vCenterbootClass)] An array with the vCenter classes
1606
+ def boot_order_update(boot_info)
1607
+ convert = lambda {|device_str|
1608
+ spl = device_str.scan(/^(nic|disk)(\d+$)/).flatten
1609
+ raise "#{device_str} is not supported" if spl.empty?
1610
+
1611
+ device = nil
1612
+ sync = "sync_#{spl[0]}s"
1613
+ (0..1).each do |_i|
1614
+ device = send(spl[0], spl[1])
1615
+ break if device.exists?
1616
+
1617
+ send(sync)
1618
+ end
1619
+
1620
+ device.boot_dev
1621
+ }
1622
+
1623
+ boot_order = boot_info.split(',').map {|str| convert.call(str) }
1624
+
1625
+ RbVmomi::VIM.VirtualMachineBootOptions({ :bootOrder => boot_order })
1626
+ end
1627
+
1628
+ # sync OpenNebula nic model with vCenter
1629
+ #
1630
+ # @param option [symbol] if :all is provided
1631
+ # the method will try to sync
1632
+ # all the nics (detached and not existing ones)
1633
+ # otherwise it will only sync
1634
+ # the nics that are not existing
1635
+ #
1636
+ # @param execute [boolean] indicates
1637
+ # if the reconfigure operation is going to
1638
+ # be executed
1639
+ def sync_nics(option = :none, execute = true)
1640
+ device_change = []
1641
+
1642
+ if option == :all
1643
+ dchange = []
1644
+
1645
+ # detached? condition indicates that
1646
+ # the nic exists in OpeNebula but not
1647
+ # in vCenter
1648
+ nics_each(:detached?) do |nic|
1649
+ dchange << {
1650
+ :operation => :remove,
1651
+ :device => nic.vc_item
1652
+ }
1653
+ end
1654
+ if !dchange.empty?
1655
+ dspec_hash = { :deviceChange => dchange }
1656
+ dspec = RbVmomi::VIM.VirtualMachineConfigSpec(dspec_hash)
1657
+ @item.ReconfigVM_Task(:spec => dspec).wait_for_completion
1658
+ end
1659
+ end
1660
+
1661
+ # no_exits? condition indicates that
1662
+ # the nic does not exist in vCenter
1663
+ nics_each(:no_exists?) do |nic|
1664
+ device_change << calculate_add_nic_spec(nic.one_item)
1665
+ end
1666
+
1667
+ return device_change unless execute
1668
+
1669
+ spec_hash = { :deviceChange => device_change }
1670
+
1671
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
1672
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
1673
+
1674
+ info_nics
1675
+ end
1676
+
1677
+ # Get required parameters to use VMware HTML Console SDK
1678
+ # To be used with the following SDK:
1679
+ # https://code.vmware.com/web/sdk/2.1.0/html-console
1680
+ #
1681
+ def html_console_parameters
1682
+ ticket = @item.AcquireTicket(:ticketType => 'webmks')
1683
+ { :ticket => ticket.ticket, :host => ticket.host,
1684
+ :port => ticket.port }
1685
+ end
1686
+
1687
+ # Synchronize the OpenNebula VM representation with vCenter VM
1688
+ #
1689
+ # if the device exists in vCenter and not in OpenNebula : detach
1690
+ # if the device exists in OpenNebula and not in vCenter : attach
1691
+ # if the device exists in both : noop
1692
+ #
1693
+ def sync(deploy = {})
1694
+ extraconfig = []
1695
+ device_change = []
1696
+
1697
+ disks = sync_disks(:all, false)
1698
+ resize_unmanaged_disks
1699
+
1700
+ if deploy[:boot] && !deploy[:boot].empty?
1701
+ boot_opts = boot_order_update(deploy[:boot])
1702
+ end
1703
+
1704
+ # changes from sync_disks
1705
+ device_change += disks[:deviceChange] if disks[:deviceChange]
1706
+ extraconfig += disks[:extraConfig] if disks[:extraConfig]
1707
+
1708
+ # get token and context
1709
+ extraconfig += extraconfig_context
1710
+
1711
+ # get file_ds
1712
+ if (files = one_item['TEMPLATE/CONTEXT/FILES_DS'])
1713
+ file_id = 0
1714
+ files.split(' ').each do |file|
1715
+ extraconfig += extraconfig_file(file, file_id)
1716
+ file_id += 1
1717
+ end
1718
+ end
1719
+
1720
+ # vnc configuration (for config_array hash)
1721
+ extraconfig += extraconfig_vnc
1722
+
1723
+ # device_change hash (nics)
1724
+ device_change += sync_nics(:all, false)
1725
+
1726
+ # Set CPU, memory and extraconfig
1727
+ num_cpus = one_item['TEMPLATE/VCPU'] || 1
1728
+ spec_hash = {
1729
+ :numCPUs => num_cpus.to_i,
1730
+ :memoryMB => one_item['TEMPLATE/MEMORY'],
1731
+ :extraConfig => extraconfig,
1732
+ :deviceChange => device_change
1733
+ }
1734
+ num_cores = one_item['TEMPLATE/TOPOLOGY/CORES'] || num_cpus.to_i
1735
+ if num_cpus.to_i % num_cores.to_i != 0
1736
+ num_cores = num_cpus.to_i
1737
+ end
1738
+ spec_hash[:numCoresPerSocket] = num_cores.to_i
1739
+
1740
+ spec_hash[:bootOptions] = boot_opts if boot_opts
1741
+
1742
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
1743
+
1744
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
1745
+ sync_extraconfig_disk(spec_hash)
1746
+ end
1747
+
1748
+ def extraconfig_file(file, id)
1749
+ path, name = file.split(':')
1750
+ name = name.gsub('\'', '')
1751
+ file_content = Base64.encode64(File.read(path))
1752
+ file_content.prepend("#{name}\n")
1753
+
1754
+ [
1755
+ { :key => "guestinfo.opennebula.file.#{id}",
1756
+ :value => file_content }
1757
+ ]
1758
+ end
1759
+
1760
+ def extraconfig_context
1761
+ context_text = "# Context variables generated by OpenNebula\n"
1762
+ one_item.each('TEMPLATE/CONTEXT/*') do |context_element|
1763
+ # next if !context_element.text
1764
+ context_text += context_element.name + "='" +
1765
+ context_element.text.gsub("'", "\'") + "'\n"
1766
+ end
1767
+
1768
+ # token
1769
+ token = File.read(File.join(VAR_LOCATION,
1770
+ 'vms',
1771
+ one_item['ID'],
1772
+ 'token.txt')).chomp rescue nil
1773
+
1774
+ context_text += "ONEGATE_TOKEN='#{token}'\n" if token
1775
+
1776
+ # context_text
1777
+ [
1778
+ { :key => 'guestinfo.opennebula.context',
1779
+ :value => Base64.encode64(context_text) }
1780
+ ]
1781
+ end
1782
+
1783
+ def extraconfig_vnc
1784
+ if one_item['TEMPLATE/GRAPHICS']
1785
+ vnc_port = one_item['TEMPLATE/GRAPHICS/PORT'] || ''
1786
+ vnc_listen = one_item['TEMPLATE/GRAPHICS/LISTEN'] || '0.0.0.0'
1787
+ vnc_keymap = one_item['TEMPLATE/GRAPHICS/KEYMAP']
1788
+
1789
+ conf =
1790
+ [
1791
+ {
1792
+ :key =>
1793
+ 'remotedisplay.vnc.enabled',
1794
+ :value =>
1795
+ 'TRUE'
1796
+ },
1797
+ {
1798
+ :key =>
1799
+ 'remotedisplay.vnc.port',
1800
+ :value =>
1801
+ vnc_port
1802
+ },
1803
+ {
1804
+ :key =>
1805
+ 'remotedisplay.vnc.ip',
1806
+ :value =>
1807
+ vnc_listen
1808
+ }
1809
+ ]
1810
+
1811
+ conf +=
1812
+ [
1813
+ {
1814
+ :key =>
1815
+ 'remotedisplay.vnc.keymap',
1816
+ :value =>
1817
+ vnc_keymap
1818
+ }
1819
+ ] if vnc_keymap
1820
+
1821
+ conf
1822
+ else
1823
+ []
1824
+ end
1825
+ end
1826
+
1827
+ # Regenerate context when devices are hot plugged (reconfigure)
1828
+ def regenerate_context
1829
+ spec_hash = { :extraConfig => extraconfig_context }
1830
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
1831
+
1832
+ begin
1833
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
1834
+ rescue StandardError => e
1835
+ error = "Cannot create snapshot for VM: #{e.message}."
1836
+
1837
+ if VCenterDriver::CONFIG[:debug_information]
1838
+ error += "\n\n#{e.backtrace}"
1839
+ end
1840
+
1841
+ raise error
1842
+ end
1843
+ end
1844
+
1845
+ # Returns an array of actions to be included in :deviceChange
1846
+ def calculate_add_nic_spec(nic, unumber = nil)
1847
+ mac = nic['MAC']
1848
+ pg_name = nic['BRIDGE']
1849
+ default =
1850
+ VCenterDriver::VIHelper
1851
+ .get_default(
1852
+ 'VM/TEMPLATE/NIC/MODEL'
1853
+ )
1854
+ tmodel = one_item['USER_TEMPLATE/NIC_DEFAULT/MODEL']
1855
+
1856
+ # got the model of the nic, first try to get the model
1857
+ # inside the nic, then the model defined by user and
1858
+ # last option model by default in vCenter Driver
1859
+ model = nic['MODEL'] || tmodel || default
1860
+ raise 'nic model cannot be empty!' if model == ''
1861
+
1862
+ vnet_ref = nic['VCENTER_NET_REF']
1863
+ backing = nil
1864
+
1865
+ # Maximum bitrate for the interface in kilobytes/second
1866
+ # for inbound traffic
1867
+ limit_in =
1868
+ nic['INBOUND_PEAK_BW'] ||
1869
+ VCenterDriver::VIHelper.get_default(
1870
+ 'VM/TEMPLATE/NIC/INBOUND_PEAK_BW'
1871
+ )
1872
+ # Maximum bitrate for the interface in kilobytes/second
1873
+ # for outbound traffic
1874
+ limit_out =
1875
+ nic['OUTBOUND_PEAK_BW'] ||
1876
+ VCenterDriver::VIHelper.get_default(
1877
+ 'VM/TEMPLATE/NIC/OUTBOUND_PEAK_BW'
1878
+ )
1879
+ limit = nil
1880
+
1881
+ if limit_in && limit_out
1882
+ limit=([limit_in.to_i, limit_out.to_i].min / 1024) * 8
1883
+ end
1884
+
1885
+ # Average bitrate for the interface in kilobytes/second
1886
+ # for inbound traffic
1887
+ rsrv_in =
1888
+ nic['INBOUND_AVG_BW'] ||
1889
+ VCenterDriver::VIHelper.get_default(
1890
+ 'VM/TEMPLATE/NIC/INBOUND_AVG_BW'
1891
+ )
1892
+ # Average bitrate for the interface in kilobytes/second
1893
+ # for outbound traffic
1894
+ rsrv_out =
1895
+ nic['OUTBOUND_AVG_BW'] ||
1896
+ VCenterDriver::VIHelper.get_default(
1897
+ 'VM/TEMPLATE/NIC/OUTBOUND_AVG_BW'
1898
+ )
1899
+ rsrv = nil
1900
+
1901
+ if rsrv_in || rsrv_out
1902
+ rsrv=([rsrv_in.to_i, rsrv_out.to_i].min / 1024) * 8
1903
+ end
1904
+
1905
+ # get the network with ref equal to vnet_ref or
1906
+ # with name equal to pg_name
1907
+ network = self['runtime.host'].network.select do |n|
1908
+ n._ref == vnet_ref || n.name == pg_name
1909
+ end
1910
+ network = network.first
1911
+
1912
+ unless network
1913
+ raise "#{pg_name} not found in #{self['runtime.host'].name}"
1914
+ end
1915
+
1916
+ # start in one, we want the next avaliable id
1917
+ card_num = 1
1918
+ @item['config.hardware.device'].each do |dv|
1919
+ card_num += 1 if VCenterDriver::Network.nic?(dv)
1920
+ end
1921
+
1922
+ nic_card = Nic.nic_model_class(model)
1923
+
1924
+ if network.class == RbVmomi::VIM::Network
1925
+ backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(
1926
+ :deviceName => pg_name,
1927
+ :network => network
1928
+ )
1929
+ elsif network.class == RbVmomi::VIM::DistributedVirtualPortgroup
1930
+ port = RbVmomi::VIM::DistributedVirtualSwitchPortConnection(
1931
+ :switchUuid =>
1932
+ network.config.distributedVirtualSwitch.uuid,
1933
+ :portgroupKey => network.key
1934
+ )
1935
+ backing =
1936
+ RbVmomi::VIM
1937
+ .VirtualEthernetCardDistributedVirtualPortBackingInfo(
1938
+ :port => port
1939
+ )
1940
+ elsif network.class == RbVmomi::VIM::OpaqueNetwork
1941
+ backing =
1942
+ RbVmomi::VIM
1943
+ .VirtualEthernetCardOpaqueNetworkBackingInfo(
1944
+ :opaqueNetworkId =>
1945
+ network.summary.opaqueNetworkId,
1946
+ :opaqueNetworkType =>
1947
+ 'nsx.LogicalSwitch'
1948
+ )
1949
+ else
1950
+ raise 'Unknown network class'
1951
+ end
1952
+
1953
+ # grab the last unitNumber to ensure the nic to be added at the end
1954
+ if !unumber
1955
+ @unic = @unic || vcenter_nics_list.map do |d|
1956
+ d.unitNumber
1957
+ end.max || 0
1958
+ unumber = @unic += 1
1959
+ else
1960
+ @unic = unumber
1961
+ end
1962
+
1963
+ card_spec = {
1964
+ :key => 0,
1965
+ :deviceInfo => {
1966
+ :label => 'net' + card_num.to_s,
1967
+ :summary => pg_name
1968
+ },
1969
+ :backing => backing,
1970
+ :addressType => mac ? 'manual' : 'generated',
1971
+ :macAddress => mac,
1972
+ :unitNumber => unumber
1973
+ }
1974
+
1975
+ if (limit || rsrv) && (limit > 0)
1976
+ ra_spec = {}
1977
+ rsrv = limit if rsrv > limit
1978
+ # The bandwidth limit for the virtual network adapter. The
1979
+ # utilization of the virtual network adapter will not exceed
1980
+ # this limit, even if there are available resources. To clear
1981
+ # the value of this property and revert it to unset, set the
1982
+ # vaule to "-1" in an update operation. Units in Mbits/sec
1983
+ ra_spec[:limit] = limit if limit
1984
+ # Amount of network bandwidth that is guaranteed to the virtual
1985
+ # network adapter. If utilization is less than reservation, the
1986
+ # resource can be used by other virtual network adapters.
1987
+ # Reservation is not allowed to exceed the value of limit if
1988
+ # limit is set. Units in Mbits/sec
1989
+ ra_spec[:reservation] = rsrv if rsrv
1990
+ # Network share. The value is used as a relative weight in
1991
+ # competing for shared bandwidth, in case of resource contention
1992
+ ra_spec[:share] =
1993
+ RbVmomi::VIM.SharesInfo(
1994
+ {
1995
+ :level => RbVmomi::VIM.SharesLevel('normal'),
1996
+ :shares => 0
1997
+ }
1998
+ )
1999
+ card_spec[:resourceAllocation] =
2000
+ RbVmomi::VIM.VirtualEthernetCardResourceAllocation(
2001
+ ra_spec
2002
+ )
2003
+ end
2004
+
2005
+ {
2006
+ :operation => :add,
2007
+ :device => nic_card.new(card_spec)
2008
+ }
2009
+ end
2010
+
2011
+ # Returns an array of actions to be included in :deviceChange
2012
+ def calculate_add_nic_spec_autogenerate_mac(nic)
2013
+ pg_name = nic['BRIDGE']
2014
+
2015
+ default =
2016
+ VCenterDriver::VIHelper.get_default(
2017
+ 'VM/TEMPLATE/NIC/MODEL'
2018
+ )
2019
+ tmodel = one_item['USER_TEMPLATE/NIC_DEFAULT/MODEL']
2020
+
2021
+ model = nic['MODEL'] || tmodel || default
2022
+
2023
+ vnet_ref = nic['VCENTER_NET_REF']
2024
+ backing = nil
2025
+
2026
+ # Maximum bitrate for the interface in kilobytes/second
2027
+ # for inbound traffic
2028
+ limit_in =
2029
+ nic['INBOUND_PEAK_BW'] ||
2030
+ VCenterDriver::VIHelper.get_default(
2031
+ 'VM/TEMPLATE/NIC/INBOUND_PEAK_BW'
2032
+ )
2033
+ # Maximum bitrate for the interface in kilobytes/second
2034
+ # for outbound traffic
2035
+ limit_out =
2036
+ nic['OUTBOUND_PEAK_BW'] ||
2037
+ VCenterDriver::VIHelper.get_default(
2038
+ 'VM/TEMPLATE/NIC/OUTBOUND_PEAK_BW'
2039
+ )
2040
+ limit = nil
2041
+
2042
+ if limit_in && limit_out
2043
+ limit=([limit_in.to_i, limit_out.to_i].min / 1024) * 8
2044
+ end
2045
+
2046
+ # Average bitrate for the interface in kilobytes/second
2047
+ # for inbound traffic
2048
+ rsrv_in =
2049
+ nic['INBOUND_AVG_BW'] ||
2050
+ VCenterDriver::VIHelper.get_default(
2051
+ 'VM/TEMPLATE/NIC/INBOUND_AVG_BW'
2052
+ )
2053
+
2054
+ # Average bitrate for the interface in kilobytes/second
2055
+ # for outbound traffic
2056
+ rsrv_out =
2057
+ nic['OUTBOUND_AVG_BW'] ||
2058
+ VCenterDriver::VIHelper.get_default(
2059
+ 'VM/TEMPLATE/NIC/OUTBOUND_AVG_BW'
2060
+ )
2061
+
2062
+ rsrv = nil
2063
+
2064
+ if rsrv_in || rsrv_out
2065
+ rsrv=([rsrv_in.to_i, rsrv_out.to_i].min / 1024) * 8
2066
+ end
2067
+
2068
+ network = self['runtime.host'].network.select do |n|
2069
+ n._ref == vnet_ref || n.name == pg_name
2070
+ end
2071
+
2072
+ network = network.first
2073
+
2074
+ card_num = 1 # start in one, we want the next available id
2075
+
2076
+ @item['config.hardware.device'].each do |dv|
2077
+ card_num += 1 if VCenterDriver::Network.nic?(dv)
2078
+ end
2079
+
2080
+ nic_card = Nic.nic_model_class(model)
2081
+
2082
+ if network.class == RbVmomi::VIM::Network
2083
+ backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(
2084
+ :deviceName => pg_name,
2085
+ :network => network
2086
+ )
2087
+ elsif network.class == RbVmomi::VIM::DistributedVirtualPortgroup
2088
+ port = RbVmomi::VIM::DistributedVirtualSwitchPortConnection(
2089
+ :switchUuid =>
2090
+ network.config.distributedVirtualSwitch.uuid,
2091
+ :portgroupKey => network.key
2092
+ )
2093
+ backing =
2094
+ RbVmomi::VIM
2095
+ .VirtualEthernetCardDistributedVirtualPortBackingInfo(
2096
+ :port => port
2097
+ )
2098
+ elsif network.class == RbVmomi::VIM::OpaqueNetwork
2099
+ backing =
2100
+ RbVmomi::VIM
2101
+ .VirtualEthernetCardOpaqueNetworkBackingInfo(
2102
+ :opaqueNetworkId => network.summary.opaqueNetworkId,
2103
+ :opaqueNetworkType => 'nsx.LogicalSwitch'
2104
+ )
2105
+ else
2106
+ raise 'Unknown network class'
2107
+ end
2108
+
2109
+ card_spec = {
2110
+ :key => 0,
2111
+ :deviceInfo => {
2112
+ :label => 'net' + card_num.to_s,
2113
+ :summary => pg_name
2114
+ },
2115
+ :backing => backing,
2116
+ :addressType => 'generated'
2117
+ }
2118
+
2119
+ if (limit || rsrv) && (limit > 0)
2120
+ ra_spec = {}
2121
+ rsrv = limit if rsrv > limit
2122
+ # The bandwidth limit for the virtual network adapter. The
2123
+ # utilization of the virtual network adapter will not exceed
2124
+ # this limit, even if there are available resources. To clear
2125
+ # the value of this property and revert it to unset, set the
2126
+ # vaule to "-1" in an update operation. Units in Mbits/sec
2127
+ ra_spec[:limit] = limit if limit
2128
+ # Amount of network bandwidth that is guaranteed to the virtual
2129
+ # network adapter. If utilization is less than reservation, the
2130
+ # resource can be used by other virtual network adapters.
2131
+ # Reservation is not allowed to exceed the value of limit if
2132
+ # limit is set. Units in Mbits/sec
2133
+ ra_spec[:reservation] = rsrv if rsrv
2134
+ # Network share. The value is used as a relative weight in
2135
+ # competing for shared bandwidth, in case of resource contention
2136
+ ra_spec[:share] =
2137
+ RbVmomi::VIM.SharesInfo(
2138
+ {
2139
+ :level =>
2140
+ RbVmomi::VIM.SharesLevel(
2141
+ 'normal'
2142
+ ),
2143
+ :shares => 0
2144
+ }
2145
+ )
2146
+ card_spec[:resourceAllocation] =
2147
+ RbVmomi::VIM.VirtualEthernetCardResourceAllocation(ra_spec)
2148
+ end
2149
+
2150
+ {
2151
+ :operation => :add,
2152
+ :device => nic_card.new(card_spec)
2153
+ }
2154
+ end
2155
+
2156
+ # Add NIC to VM
2157
+ def attach_nic(one_nic)
2158
+ spec_hash = {}
2159
+
2160
+ begin
2161
+ # A new NIC requires a vcenter spec
2162
+ attach_nic_array = []
2163
+ attach_nic_array << calculate_add_nic_spec(one_nic)
2164
+ spec_hash[:deviceChange] =
2165
+ attach_nic_array unless attach_nic_array.empty?
2166
+
2167
+ # Reconfigure VM
2168
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
2169
+
2170
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
2171
+ rescue StandardError => e
2172
+ error = "Cannot attach NIC to VM: #{e.message}."
2173
+
2174
+ if VCenterDriver::CONFIG[:debug_information]
2175
+ error += "\n\n#{e.backtrace.join("\n")}"
2176
+ end
2177
+
2178
+ raise error
2179
+ end
2180
+ end
2181
+
2182
+ # Detach NIC from VM
2183
+ def detach_nic(mac)
2184
+ spec_hash = {}
2185
+
2186
+ nic = nic(mac) rescue nil
2187
+
2188
+ return if !nic || nic.no_exists?
2189
+
2190
+ # Remove NIC from VM in the ReconfigVM_Task
2191
+ spec_hash[:deviceChange] = [
2192
+ :operation => :remove,
2193
+ :device => nic.vc_item
2194
+ ]
2195
+ begin
2196
+ @item.ReconfigVM_Task(:spec => spec_hash).wait_for_completion
2197
+ rescue StandardError => e
2198
+ error = "Cannot detach NIC from VM: #{e.message}."
2199
+
2200
+ if VCenterDriver::CONFIG[:debug_information]
2201
+ error += "\n\n#{e.backtrace.join("\n")}"
2202
+ end
2203
+
2204
+ raise error
2205
+ end
2206
+ end
2207
+
2208
+ # Detach all nics useful when removing pg and sw so they're not in use
2209
+ def detach_all_nics
2210
+ spec_hash = {}
2211
+ device_change = []
2212
+
2213
+ nics_each(:exists?) do |nic|
2214
+ device_change << {
2215
+ :operation => :remove,
2216
+ :device => nic.vc_item
2217
+ }
2218
+ end
2219
+
2220
+ return if device_change.empty?
2221
+
2222
+ # Remove NIC from VM in the ReconfigVM_Task
2223
+ spec_hash[:deviceChange] = device_change
2224
+
2225
+ begin
2226
+ @item.ReconfigVM_Task(:spec => spec_hash).wait_for_completion
2227
+ rescue StandardError => e
2228
+ error = "Cannot detach all NICs from VM: #{e.message}."
2229
+
2230
+ if VCenterDriver::CONFIG[:debug_information]
2231
+ error += "\n\n#{e.backtrace}"
2232
+ end
2233
+
2234
+ raise error
2235
+ end
2236
+ end
2237
+
2238
+ # try to get specs for new attached disks
2239
+ # using disk_each method with :no_exists? condition
2240
+ def attach_disks_specs
2241
+ attach_disk_array = []
2242
+ extraconfig = []
2243
+ attach_spod_array = []
2244
+ attach_spod_disk_info = {}
2245
+
2246
+ pos = { :ide => 0, :scsi => 0 }
2247
+ disks_each(:no_exists?) do |disk|
2248
+ disk.one_item['TYPE'] == 'CDROM' ? k = :ide : k = :scsi
2249
+
2250
+ if disk.storpod?
2251
+ spec = calculate_add_disk_spec(disk.one_item, pos[k])
2252
+ attach_spod_array << spec
2253
+
2254
+ controller_key = spec[:device].controllerKey
2255
+ unit_number = spec[:device].unitNumber
2256
+
2257
+ unit_ctrl = "#{controller_key}-#{unit_number}"
2258
+ attach_spod_disk_info[unit_ctrl] = disk.id
2259
+ else
2260
+ aspec = calculate_add_disk_spec(disk.one_item, pos[k])
2261
+ extra_key = "opennebula.mdisk.#{disk.one_item['DISK_ID']}"
2262
+ extra_value = aspec[:device].key.to_s
2263
+
2264
+ attach_disk_array << aspec
2265
+ extraconfig << { :key => extra_key, :value => extra_value }
2266
+ end
2267
+
2268
+ pos[k]+=1
2269
+ end
2270
+
2271
+ { :disks => attach_disk_array,
2272
+ :spods => attach_spod_array,
2273
+ :spod_info => attach_spod_disk_info,
2274
+ :extraconfig => extraconfig }
2275
+ end
2276
+
2277
+ # try to get specs for detached disks
2278
+ # using disk_each method with :dechaded? condition
2279
+ def detach_disks_specs
2280
+ detach_disk_array = []
2281
+ extra_config = []
2282
+ keys = disk_keys.invert
2283
+ ipool = VCenterDriver::VIHelper.one_pool(OpenNebula::ImagePool)
2284
+ disks_each(:detached?) do |d|
2285
+ key = d.key.to_s
2286
+ source = VCenterDriver::FileHelper.escape_path(d.path)
2287
+ persistent =
2288
+ VCenterDriver::VIHelper
2289
+ .find_persistent_image_by_source(
2290
+ source, ipool
2291
+ )
2292
+
2293
+ op = { :operation => :remove, :device => d.device }
2294
+ if !persistent && d.type != 'CDROM'
2295
+ op[:fileOperation] = :destroy
2296
+ end
2297
+ detach_disk_array << op
2298
+
2299
+ # Remove reference opennebula.disk if exist from vmx and cache
2300
+ extra_config << d.config(:delete) if keys[key]
2301
+ end
2302
+
2303
+ [detach_disk_array, extra_config]
2304
+ end
2305
+
2306
+ def different_key?(change_disk, vc_disk)
2307
+ change_disk[:device].controllerKey == vc_disk.controllerKey &&
2308
+ change_disk[:device].unitNumber == vc_disk.unitNumber &&
2309
+ change_disk[:device].key != vc_disk.key
2310
+ end
2311
+
2312
+ def sync_extraconfig_disk(spec_hash)
2313
+ return if spec_hash[:deviceChange].empty?
2314
+
2315
+ extraconfig_new = []
2316
+ # vCenter mob disks
2317
+ vc_disks = @item['config.hardware.device'].select do |vc_device|
2318
+ disk?(vc_device)
2319
+ end
2320
+ return unless vc_disks
2321
+
2322
+ # For each changed disk, compare with vcenter mob disk
2323
+ spec_hash[:deviceChange].each_with_index do |_device, index|
2324
+ change_disk = spec_hash[:deviceChange][index]
2325
+ vc_disks.each do |vc_disk|
2326
+ next unless different_key?(change_disk, vc_disk)
2327
+
2328
+ extraconfig_new <<
2329
+ {
2330
+ :key =>
2331
+ spec_hash[:extraConfig][index][:key],
2332
+ :value =>
2333
+ vc_disk.key.to_s
2334
+ }
2335
+ end
2336
+ end
2337
+
2338
+ return if extraconfig_new.empty?
2339
+
2340
+ spec_hash = {
2341
+ :extraConfig => extraconfig_new
2342
+ }
2343
+ spec =
2344
+ RbVmomi::VIM
2345
+ .VirtualMachineConfigSpec(
2346
+ spec_hash
2347
+ )
2348
+ @item.ReconfigVM_Task(
2349
+ :spec => spec
2350
+ ).wait_for_completion
2351
+ end
2352
+
2353
+ # sync OpenNebula disk model with vCenter
2354
+ #
2355
+ # @param option [symbol] if :all is provided the
2356
+ # method will try to sync
2357
+ # all the disks (detached and not existing ones)
2358
+ # otherwishe it will only sync
2359
+ # the disks that are not existing
2360
+ #
2361
+ # @param execute [boolean] indicates if the reconfigure operation
2362
+ # is going to
2363
+ # be executed
2364
+ def sync_disks(option = :nil, execute = true)
2365
+ info_disks
2366
+
2367
+ spec_hash = {}
2368
+
2369
+ if option == :all
2370
+ detach_op = {}
2371
+ detach_op[:deviceChange], detach_op[:extraConfig] =
2372
+ detach_disks_specs
2373
+ perform =
2374
+ !detach_op[:deviceChange].empty? ||
2375
+ !detach_op[:extraConfig].empty?
2376
+ @item
2377
+ .ReconfigVM_Task(
2378
+ :spec => detach_op
2379
+ ).wait_for_completion if perform
2380
+ end
2381
+
2382
+ a_specs = attach_disks_specs
2383
+
2384
+ if !a_specs[:spods].empty?
2385
+ spec_hash[:extraConfig] =
2386
+ create_storagedrs_disks(a_specs[:spods],
2387
+ a_specs[:spod_info])
2388
+ end
2389
+
2390
+ if !a_specs[:disks].empty?
2391
+ spec_hash[:deviceChange] = a_specs[:disks]
2392
+ spec_hash[:extraConfig] = a_specs[:extraconfig]
2393
+ end
2394
+
2395
+ return spec_hash unless execute
2396
+
2397
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
2398
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
2399
+ info_disks
2400
+ end
2401
+
2402
+ # Attach DISK to VM (hotplug)
2403
+ def attach_disk(disk)
2404
+ spec_hash = {}
2405
+ device_change = []
2406
+
2407
+ # Extract unmanaged_keys
2408
+ unmanaged_keys = disk_keys
2409
+ vc_disks = vcenter_disks_get
2410
+
2411
+ # Check if we're dealing with a StoragePod SYSTEM ds
2412
+ storpod = disk['VCENTER_DS_REF'].start_with?('group-')
2413
+
2414
+ # Check if disk being attached is already connected to the VM
2415
+ raise 'DISK is already connected to VM' if disk_attached_to_vm(
2416
+ disk, unmanaged_keys, vc_disks
2417
+ )
2418
+
2419
+ # Generate vCenter spec and reconfigure VM
2420
+ add_spec = calculate_add_disk_spec(disk)
2421
+ device_change << add_spec
2422
+ raise 'Could not generate DISK spec' if device_change.empty?
2423
+
2424
+ extra_key = "opennebula.mdisk.#{disk['DISK_ID']}"
2425
+ extra_value = add_spec[:device].key.to_s
2426
+
2427
+ spec_hash[:deviceChange] = device_change
2428
+ spec_hash[:extraConfig] =
2429
+ [{ :key => extra_key, :value => extra_value }]
2430
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
2431
+
2432
+ begin
2433
+ if storpod
2434
+ # Ask for StorageDRS recommendation
2435
+ # to reconfigure VM (AddDisk)
2436
+ sm = storagemanager
2437
+
2438
+ # Disk id is -1 as I don't know
2439
+ # what disk id is going to be set
2440
+ disk_locator = [RbVmomi::VIM.PodDiskLocator(:diskId => -1)]
2441
+
2442
+ # Disk locator is required for AddDisk
2443
+ vmpod_hash = {}
2444
+ vmpod_hash[:storagePod] = get_ds
2445
+ vmpod_hash[:disk] = disk_locator
2446
+ vmpod_config =
2447
+ RbVmomi::VIM::VmPodConfigForPlacement(
2448
+ vmpod_hash
2449
+ )
2450
+
2451
+ # The storage pod selection requires initialize
2452
+ spod_hash = {}
2453
+ spod_hash[:initialVmConfig] = [vmpod_config]
2454
+ spod_select =
2455
+ RbVmomi::VIM::StorageDrsPodSelectionSpec(
2456
+ spod_hash
2457
+ )
2458
+ storage_spec = RbVmomi::VIM.StoragePlacementSpec(
2459
+ :type => :reconfigure,
2460
+ :podSelectionSpec => spod_select,
2461
+ :vm => self['_ref'],
2462
+ :configSpec => spec
2463
+ )
2464
+
2465
+ # Query a storage placement recommendation
2466
+ result = sm
2467
+ .RecommendDatastores(
2468
+ :storageSpec => storage_spec
2469
+ ) rescue nil
2470
+
2471
+ if result.nil?
2472
+ raise 'Could not get placement '\
2473
+ 'specification for StoragePod'
2474
+ end
2475
+
2476
+ if !result.respond_to?(:recommendations) ||
2477
+ result.recommendations.empty?
2478
+ raise 'Could not get placement '\
2479
+ 'specification for StoragePod'
2480
+ end
2481
+
2482
+ # Get recommendation key to be applied
2483
+ key = result.recommendations.first.key ||= ''
2484
+
2485
+ if key.empty?
2486
+ raise 'Missing Datastore recommendation for StoragePod'
2487
+ end
2488
+
2489
+ # Apply recommendation
2490
+ sm.ApplyStorageDrsRecommendation_Task(
2491
+ :key => [key]
2492
+ ).wait_for_completion
2493
+
2494
+ # Add the key for the volatile disk to the
2495
+ # unmanaged opennebula.disk.id variables
2496
+ unit_number =
2497
+ spec_hash[:deviceChange][0][:device]
2498
+ .unitNumber
2499
+ controller_key =
2500
+ spec_hash[:deviceChange][0][:device]
2501
+ .controllerKey
2502
+ key =
2503
+ get_vcenter_disk_key(
2504
+ unit_number,
2505
+ controller_key
2506
+ )
2507
+ spec_hash = {}
2508
+ reference = {}
2509
+ reference[:key] =
2510
+ "opennebula.disk.#{disk['DISK_ID']}"
2511
+ reference[:value] = key.to_s
2512
+ spec_hash[:extraConfig] = [reference]
2513
+ @item
2514
+ .ReconfigVM_Task(
2515
+ :spec => spec_hash
2516
+ ).wait_for_completion
2517
+ else
2518
+ @item
2519
+ .ReconfigVM_Task(
2520
+ :spec => spec
2521
+ ).wait_for_completion
2522
+ end
2523
+ # Modify extraConfig if disks has a bad key
2524
+ sync_extraconfig_disk(spec_hash)
2525
+ rescue StandardError => e
2526
+ error = "Cannot attach DISK to VM: #{e.message}."
2527
+
2528
+ if VCenterDriver::CONFIG[:debug_information]
2529
+ error += "\n\n#{e.backtrace.join("\n")}"
2530
+ end
2531
+
2532
+ raise error
2533
+ end
2534
+ end
2535
+
2536
+ # Detach persistent disks to avoid incidental destruction
2537
+ def detach_persistent_disks(vm)
2538
+ spec_hash = {}
2539
+ spec_hash[:deviceChange] = []
2540
+ ipool = VCenterDriver::VIHelper.one_pool(OpenNebula::ImagePool)
2541
+ if ipool.respond_to?(:message)
2542
+ raise "Could not get OpenNebula ImagePool: #{ipool.message}"
2543
+ end
2544
+
2545
+ vm.config.hardware.device.each do |disk|
2546
+ next unless disk_or_cdrom?(disk)
2547
+
2548
+ # Let's try to find if disks is persistent
2549
+ source_unescaped = disk.backing.fileName.sub(
2550
+ /^\[(.*?)\] /, ''
2551
+ ) rescue next
2552
+ source = VCenterDriver::FileHelper.escape_path(source_unescaped)
2553
+
2554
+ persistent = VCenterDriver::VIHelper
2555
+ .find_persistent_image_by_source(
2556
+ source, ipool
2557
+ )
2558
+
2559
+ next unless persistent
2560
+
2561
+ spec_hash[:deviceChange] << {
2562
+ :operation => :remove,
2563
+ :device => disk
2564
+ }
2565
+ end
2566
+
2567
+ return if spec_hash[:deviceChange].empty?
2568
+
2569
+ begin
2570
+ vm.ReconfigVM_Task(:spec => spec_hash).wait_for_completion
2571
+ rescue StandardError => e
2572
+ error = "Cannot detach all DISKs from VM: #{e.message}."
2573
+
2574
+ if VCenterDriver::CONFIG[:debug_information]
2575
+ error += "\n\n#{e.backtrace}"
2576
+ end
2577
+
2578
+ raise error
2579
+ end
2580
+ end
2581
+
2582
+ def detach_disk(disk)
2583
+ return unless disk.exists?
2584
+
2585
+ spec_hash = {}
2586
+ spec_hash[:extraConfig] = [disk.config(:delete)]
2587
+ spec_hash[:deviceChange] = [{
2588
+ :operation => :remove,
2589
+ :device => disk.device
2590
+ }]
2591
+
2592
+ begin
2593
+ @item.ReconfigVM_Task(:spec => spec_hash).wait_for_completion
2594
+ rescue StandardError => e
2595
+ error = "Cannot detach DISK from VM: #{e.message}."
2596
+ error += "\nProbably an existing VM snapshot includes that disk"
2597
+
2598
+ if VCenterDriver::CONFIG[:debug_information]
2599
+ error += "\n\n#{e.backtrace}"
2600
+ end
2601
+
2602
+ raise error
2603
+ end
2604
+ end
2605
+
2606
+ def destroy_disk(disk)
2607
+ one_vm = one_item
2608
+
2609
+ # Check if we can detach and delete the non persistent disk:
2610
+ # - VM is terminated
2611
+ # - The disk is managed by OpenNebula
2612
+ detachable= !(one_vm['LCM_STATE'].to_i == 11 && !disk.managed?)
2613
+ detachable &&= disk.exists?
2614
+
2615
+ return unless detachable
2616
+
2617
+ detach_disk(disk)
2618
+
2619
+ # Check if we want to keep the non persistent disk
2620
+ keep_non_persistent_disks =
2621
+ VCenterDriver::CONFIG[:keep_non_persistent_disks]
2622
+
2623
+ return if keep_non_persistent_disks == true
2624
+
2625
+ disk.destroy
2626
+ @disks.delete(disk.id.to_s)
2627
+ end
2628
+
2629
+ # Get vcenter device representing DISK object (hotplug)
2630
+ def disk_attached_to_vm(disk, unmanaged_keys, vc_disks)
2631
+ img_name = ''
2632
+ device_found = nil
2633
+ disk_id = disk['DISK_ID']
2634
+ unmanaged_key = unmanaged_keys["opennebula.disk.#{disk_id}"]
2635
+
2636
+ img_name_escaped = VCenterDriver::FileHelper.get_img_name(
2637
+ disk,
2638
+ one_item['ID'],
2639
+ self['name'],
2640
+ instantiated_as_persistent?
2641
+ )
2642
+
2643
+ img_name = VCenterDriver::FileHelper.unescape_path(img_name_escaped)
2644
+
2645
+ vc_disks.each do |d|
2646
+ key_matches = (unmanaged_key && d[:key] == unmanaged_key.to_i)
2647
+ path_matches = (d[:path_wo_ds] == img_name)
2648
+
2649
+ if key_matches || path_matches
2650
+ device_found = d
2651
+ break
2652
+ end
2653
+ end
2654
+
2655
+ device_found
2656
+ end
2657
+
2658
+ def get_key(type)
2659
+ @used_keys ||= []
2660
+
2661
+ if type == 'CDROM'
2662
+ bound = 'cdrom?'
2663
+ key = 3000
2664
+ else
2665
+ bound = 'disk?'
2666
+ key = 2000
2667
+ end
2668
+
2669
+ used = @used_keys
2670
+ @item.config.hardware.device.each do |dev|
2671
+ used << dev.key
2672
+ next unless send(bound, dev)
2673
+
2674
+ key = dev.key
2675
+ end
2676
+
2677
+ loop do
2678
+ break unless used.include?(key)
2679
+
2680
+ key+=1
2681
+ end
2682
+
2683
+ @used_keys << key
2684
+
2685
+ key
2686
+ end
2687
+
2688
+ def calculate_add_disk_spec(disk, position = 0)
2689
+ img_name_escaped = VCenterDriver::FileHelper.get_img_name(
2690
+ disk,
2691
+ one_item['ID'],
2692
+ self['name'],
2693
+ instantiated_as_persistent?
2694
+ )
2695
+
2696
+ img_name = VCenterDriver::FileHelper.unescape_path(img_name_escaped)
2697
+
2698
+ type = disk['TYPE']
2699
+ size_kb = disk['SIZE'].to_i * 1024
2700
+
2701
+ if type == 'CDROM'
2702
+ # CDROM drive will be found in the IMAGE DS
2703
+ ds_ref = disk['VCENTER_DS_REF']
2704
+ ds = VCenterDriver::Storage.new_from_ref(ds_ref,
2705
+ @vi_client)
2706
+ ds_name = ds['name']
2707
+
2708
+ # CDROM can only be added when the VM is in poweroff state
2709
+ vmdk_backing = RbVmomi::VIM::VirtualCdromIsoBackingInfo(
2710
+ :datastore => ds.item,
2711
+ :fileName => "[#{ds_name}] #{img_name}"
2712
+ )
2713
+
2714
+ if @item['summary.runtime.powerState'] != 'poweredOff'
2715
+ raise 'The CDROM image can only be added as an IDE device '\
2716
+ 'when the VM is in the powered off state'
2717
+ end
2718
+
2719
+ controller, unit_number = find_free_ide_controller(position)
2720
+
2721
+ device = RbVmomi::VIM::VirtualCdrom(
2722
+ :backing => vmdk_backing,
2723
+ :key => get_key(type),
2724
+ :controllerKey => controller.key,
2725
+ :unitNumber => unit_number,
2726
+
2727
+ :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo(
2728
+ :startConnected => true,
2729
+ :connected => true,
2730
+ :allowGuestControl => true
2731
+ )
2732
+ )
2733
+
2734
+ {
2735
+ :operation => :add,
2736
+ :device => device
2737
+ }
2738
+
2739
+ else
2740
+ # TYPE is regular disk (not CDROM)
2741
+ # disk_adapter
2742
+ disk_adapter = disk['VCENTER_ADAPTER_TYPE']
2743
+ case disk_adapter
2744
+ when 'ide'
2745
+ controller, unit_number = find_free_ide_controller(position)
2746
+ else
2747
+ controller, unit_number = find_free_controller(position)
2748
+ end
2749
+ storpod = disk['VCENTER_DS_REF'].start_with?('group-')
2750
+ if storpod
2751
+ vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
2752
+ :diskMode => 'persistent',
2753
+ :fileName => ''
2754
+ )
2755
+ else
2756
+ ds = get_effective_ds(disk)
2757
+ if ds.item._ref.start_with?('group-')
2758
+ ds_object = item.datastore.first
2759
+ ds_name = ds_object.name
2760
+ else
2761
+ ds_object = ds.item
2762
+ ds_name = ds['name']
2763
+ end
2764
+ vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
2765
+ :datastore => ds_object,
2766
+ :diskMode => 'persistent',
2767
+ :fileName => "[#{ds_name}] #{img_name}"
2768
+ )
2769
+ end
2770
+
2771
+ device = RbVmomi::VIM::VirtualDisk(
2772
+ :backing => vmdk_backing,
2773
+ :capacityInKB => size_kb,
2774
+ :controllerKey => controller.key,
2775
+ :key => get_key(type),
2776
+ :unitNumber => unit_number
2777
+ )
2778
+
2779
+ config = {
2780
+ :operation => :add,
2781
+ :device => device
2782
+ }
2783
+
2784
+ # For StorageDRS vCenter must create the file
2785
+ config[:fileOperation] = :create if storpod
2786
+
2787
+ config
2788
+ end
2789
+ end
2790
+
2791
+ # Remove the MAC addresses so they cannot be in conflict
2792
+ # with OpenNebula assigned mac addresses.
2793
+ # We detach all nics from the VM
2794
+ def convert_to_template
2795
+ detach_all_nics
2796
+
2797
+ # We attach new NICs where the MAC address is assigned by vCenter
2798
+ nic_specs = []
2799
+ one_nics = one_item.retrieve_xmlelements('TEMPLATE/NIC')
2800
+ one_nics.each do |nic|
2801
+ next unless nic['OPENNEBULA_MANAGED'] &&
2802
+ nic['OPENNEBULA_MANAGED'].upcase == 'NO'
2803
+
2804
+ nic_specs <<
2805
+ calculate_add_nic_spec_autogenerate_mac(
2806
+ nic
2807
+ )
2808
+ end
2809
+
2810
+ # Reconfigure VM to add unmanaged nics
2811
+ spec_hash = {}
2812
+ spec_hash[:deviceChange] = nic_specs
2813
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(spec_hash)
2814
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
2815
+
2816
+ # Convert VM to template in vCenter
2817
+ mark_as_template
2818
+
2819
+ # Edit the Opennebula template
2820
+ one_client = OpenNebula::Client.new
2821
+ template_id = one_item['TEMPLATE/TEMPLATE_ID']
2822
+ new_template = OpenNebula::Template.new_with_id(template_id,
2823
+ one_client)
2824
+ new_template.info
2825
+
2826
+ # unlock VM Template
2827
+ new_template.unlock
2828
+
2829
+ # Update the template reference
2830
+ new_template.update("VCENTER_TEMPLATE_REF=#{@item._ref}", true)
2831
+
2832
+ # Add vCenter template name
2833
+ new_template.update("VCENTER_TEMPLATE_NAME=#{@item.name}", true)
2834
+ end
2835
+
2836
+ def resize_unmanaged_disks
2837
+ spec = { :deviceChange => [] }
2838
+ disks_each(:one?) do |d|
2839
+ next unless !d.managed? && d.new_size
2840
+
2841
+ spec[:deviceChange] << d.config(:resize)
2842
+ end
2843
+
2844
+ return if spec[:deviceChange].empty?
2845
+
2846
+ @item
2847
+ .ReconfigVM_Task(
2848
+ :spec => spec
2849
+ ).wait_for_completion
2850
+ end
2851
+
2852
+ def resize_disk(disk)
2853
+ if !disk.exists?
2854
+ size = disk.size
2855
+ sync_disks
2856
+ disk = disk(disk.id)
2857
+ disk.change_size(size)
2858
+ end
2859
+
2860
+ spec = { :deviceChange => [disk.config(:resize)] }
2861
+
2862
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
2863
+ end
2864
+
2865
+ def snapshots?
2866
+ clear('rootSnapshot')
2867
+ self['rootSnapshot'] && !self['rootSnapshot'].empty?
2868
+ end
2869
+
2870
+ def instantiated_as_persistent?
2871
+ begin
2872
+ !one_item['TEMPLATE/CLONING_TEMPLATE_ID'].nil?
2873
+ rescue StandardError
2874
+ # one_item may not be retrieved if deploy_id hasn't been set
2875
+ false
2876
+ end
2877
+ end
2878
+
2879
+ def use_linked_clone?
2880
+ one_item['USER_TEMPLATE/VCENTER_LINKED_CLONES'] &&
2881
+ one_item['USER_TEMPLATE/VCENTER_LINKED_CLONES']
2882
+ .upcase == 'YES'
2883
+ end
2884
+
2885
+ def find_free_ide_controller(_position = 0)
2886
+ free_ide_controller = nil
2887
+ ide_schema = {}
2888
+ devices = @item.config.hardware.device
2889
+
2890
+ devices.each do |dev|
2891
+ # Iteration to initialize IDE Controllers
2892
+ next unless dev.is_a? RbVmomi::VIM::VirtualIDEController
2893
+
2894
+ if ide_schema[dev.key].nil?
2895
+ ide_schema[dev.key] = {}
2896
+ end
2897
+ ide_schema[dev.key][:device] = dev
2898
+ ide_schema[dev.key][:freeUnitNumber] = [0, 1]
2899
+ end
2900
+
2901
+ # Iteration to match Disks and Cdroms with its controllers
2902
+ devices.each do |dev| # rubocop:disable Style/CombinableLoops
2903
+ first_condition = dev.is_a? RbVmomi::VIM::VirtualDisk
2904
+ second_condition = dev.is_a? RbVmomi::VIM::VirtualCdrom
2905
+ third_condition = ide_schema.key?(dev.controllerKey)
2906
+
2907
+ next unless (first_condition || second_condition) &&
2908
+ third_condition
2909
+
2910
+ ide_schema[dev.controllerKey][:freeUnitNumber]
2911
+ .delete(
2912
+ dev.unitNumber
2913
+ )
2914
+ end
2915
+
2916
+ ide_schema.keys.each do |controller|
2917
+ unless ide_schema[controller][:freeUnitNumber].empty?
2918
+ free_ide_controller = ide_schema[controller]
2919
+ break
2920
+ end
2921
+ end
2922
+
2923
+ if !free_ide_controller
2924
+ raise 'There are no free IDE controllers ' +
2925
+ 'to connect this CDROM device'
2926
+ end
2927
+
2928
+ controller = free_ide_controller[:device]
2929
+ new_unit_number = free_ide_controller[:freeUnitNumber][0]
2930
+
2931
+ [controller, new_unit_number]
2932
+ end
2933
+
2934
+ def find_free_controller(position = 0)
2935
+ free_scsi_controllers = []
2936
+ scsi_schema = {}
2937
+
2938
+ used_numbers = []
2939
+ available_numbers = []
2940
+ devices = @item.config.hardware.device
2941
+
2942
+ devices.each do |dev|
2943
+ if dev.is_a? RbVmomi::VIM::VirtualSCSIController
2944
+ if scsi_schema[dev.key].nil?
2945
+ scsi_schema[dev.key] = {}
2946
+ end
2947
+
2948
+ used_numbers << dev.scsiCtlrUnitNumber
2949
+ scsi_schema[dev.key][:device] = dev
2950
+ end
2951
+
2952
+ next if dev.class != RbVmomi::VIM::VirtualDisk
2953
+
2954
+ used_numbers << dev.unitNumber
2955
+ end
2956
+
2957
+ 15.times do |scsi_id|
2958
+ available_numbers <<
2959
+ scsi_id if used_numbers.grep(scsi_id).length <= 0
2960
+ end
2961
+
2962
+ scsi_schema.keys.each do |controller|
2963
+ free_scsi_controllers <<
2964
+ scsi_schema[controller][:device].deviceInfo.label
2965
+ end
2966
+
2967
+ if !free_scsi_controllers.empty?
2968
+ available_controller_label = free_scsi_controllers[0]
2969
+ else
2970
+ add_new_scsi(scsi_schema, devices)
2971
+ return find_free_controller
2972
+ end
2973
+
2974
+ controller = nil
2975
+
2976
+ devices.each do |device|
2977
+ if device.deviceInfo.label == available_controller_label
2978
+ controller = device
2979
+ break
2980
+ end
2981
+ end
2982
+
2983
+ new_unit_number = available_numbers.sort[position]
2984
+
2985
+ [controller, new_unit_number]
2986
+ end
2987
+
2988
+ def add_new_scsi(scsi_schema, devices)
2989
+ controller = nil
2990
+
2991
+ if scsi_schema.keys.length >= 4
2992
+ raise 'Cannot add a new controller, maximum is 4.'
2993
+ end
2994
+
2995
+ scsi_key = 0
2996
+ scsi_number = 0
2997
+
2998
+ if !scsi_schema.keys.empty? && scsi_schema.keys.length < 4
2999
+ scsi_key =
3000
+ scsi_schema.keys.max + 1
3001
+ scsi_number =
3002
+ scsi_schema[scsi_schema.keys.max][:device].busNumber + 1
3003
+ end
3004
+
3005
+ controller_device = RbVmomi::VIM::VirtualLsiLogicController(
3006
+ :key => scsi_key,
3007
+ :busNumber => scsi_number,
3008
+ :sharedBus => :noSharing
3009
+ )
3010
+
3011
+ device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
3012
+ :device => controller_device,
3013
+ :operation => :add
3014
+ )
3015
+
3016
+ vm_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec(
3017
+ :deviceChange => [device_config_spec]
3018
+ )
3019
+
3020
+ @item.ReconfigVM_Task(:spec => vm_config_spec).wait_for_completion
3021
+
3022
+ devices.each do |device|
3023
+ next unless first_ &&
3024
+ device.key == scsi_key
3025
+
3026
+ controller = device.deviceInfo.label
3027
+ end
3028
+
3029
+ controller
3030
+ end
3031
+
3032
+ # Create a snapshot for the VM
3033
+ def create_snapshot(snap_id, snap_name)
3034
+ memory_dumps = true
3035
+ memory_dumps = CONFIG[:memory_dumps] if CONFIG[:memory_dumps]
3036
+
3037
+ snapshot_hash = {
3038
+ :name => snap_id,
3039
+ :description => "OpenNebula Snapshot: #{snap_name}",
3040
+ :memory => memory_dumps,
3041
+ :quiesce => true
3042
+ }
3043
+
3044
+ begin
3045
+ @item.CreateSnapshot_Task(snapshot_hash).wait_for_completion
3046
+ rescue StandardError => e
3047
+ error = "Cannot create snapshot for VM: #{e.message}."
3048
+
3049
+ if VCenterDriver::CONFIG[:debug_information]
3050
+ error += "\n\n#{e.backtrace.join("\n")}"
3051
+ end
3052
+
3053
+ raise error
3054
+ end
3055
+
3056
+ snap_id
3057
+ end
3058
+
3059
+ # Revert to a VM snapshot
3060
+ def revert_snapshot(snap_id)
3061
+ snapshot_list = self['snapshot.rootSnapshotList']
3062
+ snapshot = find_snapshot_in_list(snapshot_list, snap_id)
3063
+
3064
+ return unless snapshot
3065
+
3066
+ begin
3067
+ revert_snapshot_hash = { :_this => snapshot }
3068
+ snapshot
3069
+ .RevertToSnapshot_Task(
3070
+ revert_snapshot_hash
3071
+ ).wait_for_completion
3072
+ rescue StandardError => e
3073
+ error = "Cannot revert snapshot of VM: #{e.message}."
3074
+
3075
+ if VCenterDriver::CONFIG[:debug_information]
3076
+ error += "\n\n#{e.backtrace.join("\n")}"
3077
+ end
3078
+
3079
+ raise error
3080
+ end
3081
+ end
3082
+
3083
+ # Delete VM snapshot
3084
+ def delete_snapshot(snap_id)
3085
+ snapshot_list = self['snapshot.rootSnapshotList']
3086
+ snapshot = find_snapshot_in_list(snapshot_list, snap_id)
3087
+
3088
+ return unless snapshot
3089
+
3090
+ begin
3091
+ delete_snapshot_hash = {
3092
+ :_this => snapshot,
3093
+ :removeChildren => false
3094
+ }
3095
+ snapshot
3096
+ .RemoveSnapshot_Task(
3097
+ delete_snapshot_hash
3098
+ ).wait_for_completion
3099
+ rescue StandardError => e
3100
+ error = "Cannot delete snapshot of VM: #{e.message}."
3101
+
3102
+ if VCenterDriver::CONFIG[:debug_information]
3103
+ error += "\n\n#{e.backtrace.join("\n")}"
3104
+ end
3105
+
3106
+ raise error
3107
+ end
3108
+ end
3109
+
3110
+ def find_snapshot_in_list(list, snap_id)
3111
+ list.each do |i|
3112
+ return i.snapshot if i.name == snap_id.to_s
3113
+
3114
+ unless i.childSnapshotList.empty?
3115
+ snap = find_snapshot_in_list(i.childSnapshotList, snap_id)
3116
+ return snap if snap
3117
+ end
3118
+ end rescue nil
3119
+
3120
+ nil
3121
+ end
3122
+
3123
+ def migrate(config = {})
3124
+ if config.empty?
3125
+ raise 'You need at least 1 parameter to perform a migration'
3126
+ end
3127
+
3128
+ begin
3129
+ # retrieve host from DRS
3130
+ one_cluster = config[:cluster]
3131
+ resourcepool = one_cluster.item.resourcePool
3132
+ datastore = config[:datastore]
3133
+
3134
+ if datastore
3135
+ relocate_spec_params = {
3136
+ :folder => @item.parent,
3137
+ :datastore => datastore
3138
+ }
3139
+
3140
+ unless config[:same_host]
3141
+ relocate_spec_params[:pool] = resourcepool
3142
+ end
3143
+
3144
+ if config[:esx_migration_list].is_a?(String)
3145
+ if config[:esx_migration_list]==''
3146
+ relocate_spec_params[:host] =
3147
+ config[:cluster].item.host.sample
3148
+ elsif config[:esx_migration_list]!='Selected_by_DRS'
3149
+ hostnames = config[:esx_migration_list].split(' ')
3150
+ hostname = hostnames.sample
3151
+ host_moref = one_cluster.hostname_to_moref(hostname)
3152
+ relocate_spec_params[:host] = host_moref
3153
+ end
3154
+ end
3155
+
3156
+ relocate_spec =
3157
+ RbVmomi::VIM
3158
+ .VirtualMachineRelocateSpec(
3159
+ relocate_spec_params
3160
+ )
3161
+ @item.RelocateVM_Task(
3162
+ :spec => relocate_spec,
3163
+ :priority => 'defaultPriority'
3164
+ ).wait_for_completion
3165
+ else
3166
+ migrate_spec_params = {
3167
+ :priority => 'defaultPriority'
3168
+ }
3169
+
3170
+ unless config[:same_host]
3171
+ migrate_spec_params[:pool] = resourcepool
3172
+ end
3173
+
3174
+ @item.MigrateVM_Task(
3175
+ migrate_spec_params
3176
+ ).wait_for_completion
3177
+ end
3178
+ rescue StandardError => e
3179
+ error = "Cannot migrate VM: #{e.message}."
3180
+
3181
+ if VCenterDriver::CONFIG[:debug_information]
3182
+ error += "\n\n#{e.backtrace.join("\n")}"
3183
+ end
3184
+
3185
+ raise error
3186
+ end
3187
+ end
3188
+
3189
+ ########################################################################
3190
+ # actions
3191
+ ########################################################################
3192
+
3193
+ def shutdown
3194
+ return if powered_off?
3195
+
3196
+ begin
3197
+ if vm_tools?
3198
+ @item.ShutdownGuest
3199
+ else
3200
+ poweroff_hard
3201
+ end
3202
+ rescue RbVmomi::Fault => e
3203
+ error = e.message.split(':').first
3204
+ raise e.message if error != 'InvalidPowerState'
3205
+ end
3206
+ timeout = CONFIG[:vm_poweron_wait_default]
3207
+ wait_timeout(:powered_off?, timeout)
3208
+ end
3209
+
3210
+ def destroy
3211
+ @item.Destroy_Task.wait_for_completion
3212
+ end
3213
+
3214
+ def mark_as_template
3215
+ @item.MarkAsTemplate
3216
+ end
3217
+
3218
+ def reset
3219
+ @item.ResetVM_Task.wait_for_completion
3220
+ end
3221
+
3222
+ def suspend
3223
+ @item.SuspendVM_Task.wait_for_completion
3224
+ end
3225
+
3226
+ def reboot
3227
+ @item.RebootGuest
3228
+ end
3229
+
3230
+ def poweron(set_running = false)
3231
+ begin
3232
+ @item.PowerOnVM_Task.wait_for_completion
3233
+ rescue RbVmomi::Fault => e
3234
+ error = e.message.split(':').first
3235
+ raise e.message if error != 'InvalidPowerState'
3236
+ end
3237
+ # opennebula.running flag
3238
+ set_running(true, true) if set_running
3239
+
3240
+ timeout = CONFIG[:vm_poweron_wait_default]
3241
+ wait_timeout(:powered_on?, timeout)
3242
+ end
3243
+
3244
+ def powered_on?
3245
+ @item.runtime.powerState == 'poweredOn'
3246
+ end
3247
+
3248
+ def powered_off?
3249
+ @item.runtime.powerState == 'poweredOff'
3250
+ end
3251
+
3252
+ def poweroff_hard
3253
+ @item.PowerOffVM_Task.wait_for_completion
3254
+ end
3255
+
3256
+ def remove_all_snapshots(consolidate = true)
3257
+ @item
3258
+ .RemoveAllSnapshots_Task(
3259
+ { :consolidate => consolidate }
3260
+ ).wait_for_completion
3261
+ info_disks
3262
+ end
3263
+
3264
+ def vm_tools?
3265
+ @item.guest.toolsRunningStatus == 'guestToolsRunning'
3266
+ end
3267
+
3268
+ def set_running(state, execute = true)
3269
+ state ? value = 'yes' : value = 'no'
3270
+
3271
+ config_array = [
3272
+ { :key => 'opennebula.vm.running', :value => value }
3273
+ ]
3274
+
3275
+ return config_array unless execute
3276
+
3277
+ spec = RbVmomi::VIM.VirtualMachineConfigSpec(
3278
+ { :extraConfig => config_array }
3279
+ )
3280
+
3281
+ @item.ReconfigVM_Task(:spec => spec).wait_for_completion
3282
+ end
3283
+
3284
+ # STATIC MEMBERS, ROUTINES AND CONSTRUCTORS
3285
+ ########################################################################
3286
+
3287
+ def self.get_vm(opts = {})
3288
+ # try to retrieve machine from name
3289
+ if opts[:name]
3290
+ matches = opts[:name].match(/^one-(\d*)(-(.*))?$/)
3291
+ if matches
3292
+ id = matches[1]
3293
+ one_vm = VCenterDriver::VIHelper.one_item(
3294
+ OpenNebula::VirtualMachine, id, false
3295
+ )
3296
+ end
3297
+ end
3298
+
3299
+ if one_vm.nil?
3300
+ one_vm = VCenterDriver::VIHelper
3301
+ .find_by_ref(
3302
+ OpenNebula::VirtualMachinePool,
3303
+ 'DEPLOY_ID',
3304
+ opts[:ref],
3305
+ opts[:vc_uuid],
3306
+ opts[:pool]
3307
+ )
3308
+ end
3309
+
3310
+ one_vm
3311
+ end
3312
+
3313
+ # Migrate a VM to another cluster and/or datastore
3314
+ # @params [int] vm_id ID of the VM to be migrated
3315
+ # @params [String] src_host Name of the source cluster
3316
+ # @params [String] dst_host Name of the target cluster
3317
+ # @params [Bool] hot_ds Wether this is a DS migration
3318
+ # with the VM running or not
3319
+ # @params [int] ds Destination datastore ID
3320
+ def self.migrate_routine(
3321
+ vm_id,
3322
+ src_host,
3323
+ dst_host,
3324
+ hot_ds = false,
3325
+ ds = nil
3326
+ )
3327
+ one_client = OpenNebula::Client.new
3328
+ pool = OpenNebula::HostPool.new(one_client)
3329
+ pool.info
3330
+
3331
+ src_id = pool["/HOST_POOL/HOST[NAME='#{src_host}']/ID"].to_i
3332
+ dst_id = pool["/HOST_POOL/HOST[NAME='#{dst_host}']/ID"].to_i
3333
+
3334
+ # different destination ds
3335
+ if ds
3336
+ ds_pool = OpenNebula::DatastorePool.new(one_client)
3337
+ ds_pool.info
3338
+ vcenter_ds_red =
3339
+ "/DATASTORE_POOL/DATASTORE[ID='#{ds}']" +
3340
+ '/TEMPLATE/VCENTER_DS_REF'
3341
+ datastore = ds_pool[vcenter_ds_red]
3342
+ end
3343
+
3344
+ vi_client = VCenterDriver::VIClient.new_from_host(src_id)
3345
+
3346
+ # required one objects
3347
+ vm = OpenNebula::VirtualMachine.new_with_id(vm_id, one_client)
3348
+ dst_host = OpenNebula::Host.new_with_id(dst_id, one_client)
3349
+
3350
+ # get info
3351
+ vm.info
3352
+ dst_host.info
3353
+
3354
+ esx_migration_list = dst_host['/HOST/TEMPLATE/ESX_MIGRATION_LIST']
3355
+
3356
+ # required vcenter objects
3357
+ vc_vm = VCenterDriver::VirtualMachine
3358
+ .new_without_id(
3359
+ vi_client,
3360
+ vm['/VM/DEPLOY_ID']
3361
+ )
3362
+
3363
+ vc_vm.vm_id = vm_id
3364
+
3365
+ ccr_ref = dst_host['/HOST/TEMPLATE/VCENTER_CCR_REF']
3366
+ vc_host = VCenterDriver::ClusterComputeResource.new_from_ref(
3367
+ ccr_ref, vi_client
3368
+ )
3369
+
3370
+ config = { :cluster => vc_host }
3371
+
3372
+ config[:same_host] = src_id == dst_id
3373
+
3374
+ config[:datastore] = datastore if datastore
3375
+ if hot_ds
3376
+ config[:esx_migration_list] =
3377
+ esx_migration_list if esx_migration_list
3378
+ else
3379
+ config[:esx_migration_list] = 'Selected_by_DRS'
3380
+ end
3381
+
3382
+ vc_vm.migrate(config)
3383
+
3384
+ vm.replace({ 'VCENTER_CCR_REF' => ccr_ref })
3385
+ end
3386
+
3387
+ # Try to build the vcenterdriver virtualmachine without
3388
+ # any opennebula id or object, this constructor can find
3389
+ # inside the opennebula pool until match
3390
+ #
3391
+ # @param vi_client [vi_client] the vcenterdriver client
3392
+ # that allows the connection
3393
+ # @param ref [String] vcenter ref to the vm
3394
+ # @param opts [Hash] object with pairs that could
3395
+ # contain multiple option
3396
+ # :vc_uuid: give the vcenter uuid directly
3397
+ # :name: the vcenter vm name for extract the opennebula id
3398
+ #
3399
+ # @return [vcenterdriver::vm] the virtual machine
3400
+ def self.new_from_ref(vi_client, ref, name, opts = {})
3401
+ unless opts[:vc_uuid]
3402
+ opts[:vc_uuid] = vi_client.vim.serviceContent.about.instanceUuid
3403
+ end
3404
+
3405
+ opts[:name] = name
3406
+ opts[:ref] = ref
3407
+
3408
+ one_vm = VCenterDriver::VirtualMachine.get_vm(opts)
3409
+
3410
+ new_one(vi_client, ref, one_vm)
3411
+ end
3412
+
3413
+ # build a vcenterdriver virtual machine from a template
3414
+ # this function is used to instantiate vcenter vms
3415
+ #
3416
+ # @param vi_client [vi_client] the vcenterdriver
3417
+ # client that allows the connection
3418
+ # @param drv_action [xmlelement] driver_action that contains the info
3419
+ # @param id [int] the if of the opennebula virtual machine
3420
+ #
3421
+ # @return [vcenterdriver::vm] the virtual machine
3422
+ def self.new_from_clone(vi_client, drv_action, id)
3423
+ new(vi_client, nil, id).tap do |vm|
3424
+ vm.clone_vm(drv_action)
3425
+ end
3426
+ end
3427
+
3428
+ # build a vcenterdriver virtual machine
3429
+ # with the vmware item already linked
3430
+ #
3431
+ # @param vm_item the vmware VM item that it's going to be associated
3432
+ #
3433
+ # @return [vcenterdriver::vm] the virtual machine
3434
+ def self.new_with_item(vm_item)
3435
+ new(nil, nil, -1).tap do |vm|
3436
+ vm.item_update(vm_item)
3437
+ end
3438
+ end
3439
+
3440
+ # build a vcenterdriver virtual machine
3441
+ # with the opennebula object linked
3442
+ #
3443
+ # @param vi_client [vi_client] the vcenterdriver
3444
+ # client that allows the connection
3445
+ # @param ref [String] vcenter ref to the vm
3446
+ # @param one_item [one::vm] xmlelement of opennebula
3447
+ #
3448
+ # @return [vcenterdriver::vm] the virtual machine
3449
+ def self.new_one(vi_client, ref, one_item)
3450
+ id = one_item['ID'] || one_item['VM/ID'] rescue -1
3451
+
3452
+ new(vi_client, ref, id).tap do |vm|
3453
+ if one_item.instance_of?(OpenNebula::VirtualMachine)
3454
+ vm.one_item = one_item
3455
+ end
3456
+ end
3457
+ end
3458
+
3459
+ # build a vcenterdriver virtual machine
3460
+ # without opennebula object link, use id = -1 instead
3461
+ #
3462
+ # @param vi_client [vi_client] the vcenterdriver client
3463
+ # that allows the connection
3464
+ # @param ref [String] vcenter ref to the vm
3465
+ #
3466
+ # @return [vcenterdriver::vm] the virtual machine
3467
+ def self.new_without_id(vi_client, ref)
3468
+ new(vi_client, ref, -1)
3469
+ end
3470
+
3471
+ ########################################################################
3472
+
3473
+ end
3474
+ # class VirtualMachine
3475
+
3476
+ end
3477
+ # module VCenterDriver