vmpooler 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32b41fec26ba54f25cb6186a6dc1b97904a855f0e5ceeca19978c4c614b5ef55
4
- data.tar.gz: 1937db66b56b998fe4030ed133eaad5bbabd15646d3786dfa1539a8ee6e35a95
3
+ metadata.gz: 69a8b533d59906b740d63c48c2f237bac2d42c3bbc770ed178e41f9981c7f955
4
+ data.tar.gz: '0609a290e75b7deb1f167a059b6342c43ce7942e600c7f67ba29b3389f411b59'
5
5
  SHA512:
6
- metadata.gz: 747e7156ec196e938a41ffcc7857d01c32d9412b28ce20bea8f52edc6d6579118c7f4d458bdca70d8765b1ce081e44926b0c5560081e6fa27d74110262ce1e2d
7
- data.tar.gz: e77c0fe360af30347f20ada5cd34879087b4775aff81914ee41c83639d7a9c91951dc8a5cbf0f2028eabd65312d96e94a95bcc81457de3d1c897d121a79282c2
6
+ metadata.gz: bd7da108e63619bb0f7a80d1567ee38e6c05c3d87d77982fa81d117612577eb7480940f39c910c8d7fb0ec373972233a304f7bc63e80dae35c7d1f0ba9edddf4
7
+ data.tar.gz: d7023cc35609431f37f55a74fd07312afaefab627558ad55f9d03eaa320dce8929ffdb36fc4ca2ab6d0a299a1d93820a2767714680813e3f7f2fbb78fac01b27
@@ -628,7 +628,7 @@ module Vmpooler
628
628
  end
629
629
 
630
630
  # @return [Array] - a list of used providers from the config file, defaults to the default providers
631
- # ie. ["vsphere", "dummy"]
631
+ # ie. ["dummy"]
632
632
  def used_providers
633
633
  pools = config[:pools] || []
634
634
  @used_providers ||= (pools.map { |pool| pool[:provider] || pool['provider'] }.compact + default_providers).uniq
@@ -638,7 +638,7 @@ module Vmpooler
638
638
  # note: vsphere is the default if user does not specify although this should not be
639
639
  # if vsphere is to no longer be loaded by default please remove
640
640
  def default_providers
641
- @default_providers ||= %w[vsphere dummy]
641
+ @default_providers ||= %w[dummy]
642
642
  end
643
643
 
644
644
  def get_pool_name_for_vm(vm_name, redis)
@@ -1561,8 +1561,8 @@ module Vmpooler
1561
1561
  # Set default provider for all pools that do not have one defined
1562
1562
  $config[:pools].each do |pool|
1563
1563
  if pool['provider'].nil?
1564
- $logger.log('d', "[!] Setting provider for pool '#{pool['name']}' to 'vsphere' as default")
1565
- pool['provider'] = 'vsphere'
1564
+ $logger.log('d', "[!] Setting provider for pool '#{pool['name']}' to 'dummy' as default")
1565
+ pool['provider'] = 'dummy'
1566
1566
  end
1567
1567
  end
1568
1568
 
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vmpooler
4
- VERSION = '1.3.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/vmpooler.rb CHANGED
@@ -7,7 +7,6 @@ module Vmpooler
7
7
  require 'net/ldap'
8
8
  require 'open-uri'
9
9
  require 'pickup'
10
- require 'rbvmomi'
11
10
  require 'redis'
12
11
  require 'set'
13
12
  require 'sinatra/base'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vmpooler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-15 00:00:00.000000000 Z
11
+ date: 2021-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -226,26 +226,6 @@ dependencies:
226
226
  - - "~>"
227
227
  - !ruby/object:Gem::Version
228
228
  version: '13.0'
229
- - !ruby/object:Gem::Dependency
230
- name: rbvmomi
231
- requirement: !ruby/object:Gem::Requirement
232
- requirements:
233
- - - ">="
234
- - !ruby/object:Gem::Version
235
- version: '2.1'
236
- - - "<"
237
- - !ruby/object:Gem::Version
238
- version: '4.0'
239
- type: :runtime
240
- prerelease: false
241
- version_requirements: !ruby/object:Gem::Requirement
242
- requirements:
243
- - - ">="
244
- - !ruby/object:Gem::Version
245
- version: '2.1'
246
- - - "<"
247
- - !ruby/object:Gem::Version
248
- version: '4.0'
249
229
  - !ruby/object:Gem::Dependency
250
230
  name: redis
251
231
  requirement: !ruby/object:Gem::Requirement
@@ -464,10 +444,10 @@ files:
464
444
  - lib/vmpooler/providers.rb
465
445
  - lib/vmpooler/providers/base.rb
466
446
  - lib/vmpooler/providers/dummy.rb
467
- - lib/vmpooler/providers/vsphere.rb
468
447
  - lib/vmpooler/public/bootstrap.min.css
469
448
  - lib/vmpooler/public/img/bg.png
470
449
  - lib/vmpooler/public/img/logo.gif
450
+ - lib/vmpooler/public/img/logo.png
471
451
  - lib/vmpooler/public/img/spinner.svg
472
452
  - lib/vmpooler/public/img/subtle_dots.png
473
453
  - lib/vmpooler/public/img/textured_paper.png
@@ -499,7 +479,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
499
479
  - !ruby/object:Gem::Version
500
480
  version: '0'
501
481
  requirements: []
502
- rubygems_version: 3.0.8
482
+ rubyforge_project:
483
+ rubygems_version: 2.7.6.2
503
484
  signing_key:
504
485
  specification_version: 4
505
486
  summary: vmpooler provides configurable pools of instantly-available (running) virtual
@@ -1,1172 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'vmpooler/providers/base'
4
- require 'bigdecimal'
5
- require 'bigdecimal/util'
6
-
7
- module Vmpooler
8
- class PoolManager
9
- class Provider
10
- class VSphere < Vmpooler::PoolManager::Provider::Base
11
- # The connection_pool method is normally used only for testing
12
- attr_reader :connection_pool
13
-
14
- def initialize(config, logger, metrics, redis_connection_pool, name, options)
15
- super(config, logger, metrics, redis_connection_pool, name, options)
16
-
17
- task_limit = global_config[:config].nil? || global_config[:config]['task_limit'].nil? ? 10 : global_config[:config]['task_limit'].to_i
18
- # The default connection pool size is:
19
- # Whatever is biggest from:
20
- # - How many pools this provider services
21
- # - Maximum number of cloning tasks allowed
22
- # - Need at least 2 connections so that a pool can have inventory functions performed while cloning etc.
23
- default_connpool_size = [provided_pools.count, task_limit, 2].max
24
- connpool_size = provider_config['connection_pool_size'].nil? ? default_connpool_size : provider_config['connection_pool_size'].to_i
25
- # The default connection pool timeout should be quite large - 60 seconds
26
- connpool_timeout = provider_config['connection_pool_timeout'].nil? ? 60 : provider_config['connection_pool_timeout'].to_i
27
- logger.log('d', "[#{name}] ConnPool - Creating a connection pool of size #{connpool_size} with timeout #{connpool_timeout}")
28
- @connection_pool = Vmpooler::PoolManager::GenericConnectionPool.new(
29
- metrics: metrics,
30
- connpool_type: 'provider_connection_pool',
31
- connpool_provider: name,
32
- size: connpool_size,
33
- timeout: connpool_timeout
34
- ) do
35
- logger.log('d', "[#{name}] Connection Pool - Creating a connection object")
36
- # Need to wrap the vSphere connection object in another object. The generic connection pooler will preserve
37
- # the object reference for the connection, which means it cannot "reconnect" by creating an entirely new connection
38
- # object. Instead by wrapping it in a Hash, the Hash object reference itself never changes but the content of the
39
- # Hash can change, and is preserved across invocations.
40
- new_conn = connect_to_vsphere
41
- { connection: new_conn }
42
- end
43
- @provider_hosts = {}
44
- @provider_hosts_lock = Mutex.new
45
- @redis = redis_connection_pool
46
- end
47
-
48
- # name of the provider class
49
- def name
50
- 'vsphere'
51
- end
52
-
53
- def folder_configured?(folder_title, base_folder, configured_folders, whitelist)
54
- return true if whitelist&.include?(folder_title)
55
- return false unless configured_folders.keys.include?(folder_title)
56
- return false unless configured_folders[folder_title] == base_folder
57
-
58
- true
59
- end
60
-
61
- def destroy_vm_and_log(vm_name, vm_object, pool, data_ttl)
62
- try = 0 if try.nil?
63
- max_tries = 3
64
- @redis.with_metrics do |redis|
65
- redis.multi
66
- redis.srem("vmpooler__completed__#{pool}", vm_name)
67
- redis.hdel("vmpooler__active__#{pool}", vm_name)
68
- redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now)
69
-
70
- # Auto-expire metadata key
71
- redis.expire("vmpooler__vm__#{vm_name}", (data_ttl * 60 * 60))
72
- redis.exec
73
- end
74
-
75
- start = Time.now
76
-
77
- if vm_object.is_a? RbVmomi::VIM::Folder
78
- logger.log('s', "[!] [#{pool}] '#{vm_name}' is a folder, bailing on destroying")
79
- raise('Expected VM, but received a folder object')
80
- end
81
- vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn'
82
- vm_object.Destroy_Task.wait_for_completion
83
-
84
- finish = format('%<time>.2f', time: Time.now - start)
85
- logger.log('s', "[-] [#{pool}] '#{vm_name}' destroyed in #{finish} seconds")
86
- metrics.timing("destroy.#{pool}", finish)
87
- rescue RuntimeError
88
- raise
89
- rescue StandardError => e
90
- try += 1
91
- logger.log('s', "[!] [#{pool}] failed to destroy '#{vm_name}' with an error: #{e}")
92
- try >= max_tries ? raise : retry
93
- end
94
-
95
- def destroy_folder_and_children(folder_object)
96
- vms = {}
97
- data_ttl = $config[:redis]['data_ttl'].to_i
98
- folder_name = folder_object.name
99
- unless folder_object.childEntity.count == 0
100
- folder_object.childEntity.each do |vm|
101
- vms[vm.name] = vm
102
- end
103
-
104
- vms.each do |vm_name, vm_object|
105
- destroy_vm_and_log(vm_name, vm_object, folder_name, data_ttl)
106
- end
107
- end
108
- destroy_folder(folder_object)
109
- end
110
-
111
- def destroy_folder(folder_object)
112
- try = 0 if try.nil?
113
- max_tries = 3
114
- logger.log('s', "[-] [#{folder_object.name}] removing unconfigured folder")
115
- folder_object.Destroy_Task.wait_for_completion
116
- rescue StandardError
117
- try += 1
118
- try >= max_tries ? raise : retry
119
- end
120
-
121
- def purge_unconfigured_folders(base_folders, configured_folders, whitelist)
122
- @connection_pool.with_metrics do |pool_object|
123
- connection = ensured_vsphere_connection(pool_object)
124
-
125
- base_folders.each do |base_folder|
126
- folder_children = get_folder_children(base_folder, connection)
127
- next if folder_children.empty?
128
-
129
- folder_children.each do |folder_hash|
130
- folder_hash.each do |folder_title, folder_object|
131
- destroy_folder_and_children(folder_object) unless folder_configured?(folder_title, base_folder, configured_folders, whitelist)
132
- end
133
- end
134
- end
135
- end
136
- end
137
-
138
- def get_folder_children(folder_name, connection)
139
- folders = []
140
-
141
- propSpecs = { # rubocop:disable Naming/VariableName
142
- entity: self,
143
- inventoryPath: folder_name
144
- }
145
- folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
146
-
147
- return folders if folder_object.nil?
148
-
149
- folder_object.childEntity.each do |folder|
150
- next unless folder.is_a? RbVmomi::VIM::Folder
151
-
152
- folders << { folder.name => folder }
153
- end
154
-
155
- folders
156
- end
157
-
158
- def vms_in_pool(pool_name)
159
- vms = []
160
- @connection_pool.with_metrics do |pool_object|
161
- connection = ensured_vsphere_connection(pool_object)
162
- folder_object = find_vm_folder(pool_name, connection)
163
-
164
- return vms if folder_object.nil?
165
-
166
- folder_object.childEntity.each do |vm|
167
- vms << { 'name' => vm.name } if vm.is_a? RbVmomi::VIM::VirtualMachine
168
- end
169
- end
170
- vms
171
- end
172
-
173
- def select_target_hosts(target, cluster, datacenter)
174
- percentage = 100
175
- dc = "#{datacenter}_#{cluster}"
176
- @provider_hosts_lock.synchronize do
177
- begin
178
- target[dc] = {} unless target.key?(dc)
179
- target[dc]['checking'] = true
180
- hosts_hash = find_least_used_hosts(cluster, datacenter, percentage)
181
- target[dc] = hosts_hash
182
- rescue StandardError
183
- target[dc] = {}
184
- raise
185
- ensure
186
- target[dc]['check_time_finished'] = Time.now
187
- end
188
- end
189
- end
190
-
191
- def run_select_hosts(pool_name, target)
192
- now = Time.now
193
- max_age = @config[:config]['host_selection_max_age'] || 60
194
- loop_delay = 5
195
- datacenter = get_target_datacenter_from_config(pool_name)
196
- cluster = get_target_cluster_from_config(pool_name)
197
- raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
198
- raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
199
-
200
- dc = "#{datacenter}_#{cluster}"
201
- unless target.key?(dc)
202
- select_target_hosts(target, cluster, datacenter)
203
- return
204
- end
205
- wait_for_host_selection(dc, target, loop_delay, max_age) if target[dc].key?('checking')
206
- select_target_hosts(target, cluster, datacenter) if target[dc].key?('check_time_finished') && now - target[dc]['check_time_finished'] > max_age
207
- end
208
-
209
- def wait_for_host_selection(dc, target, maxloop = 0, loop_delay = 1, max_age = 60)
210
- loop_count = 1
211
- until target.key?(dc) && target[dc].key?('check_time_finished')
212
- sleep(loop_delay)
213
- unless maxloop == 0
214
- break if loop_count >= maxloop
215
-
216
- loop_count += 1
217
- end
218
- end
219
- return unless target[dc].key?('check_time_finished')
220
-
221
- loop_count = 1
222
- while Time.now - target[dc]['check_time_finished'] > max_age
223
- sleep(loop_delay)
224
- unless maxloop == 0
225
- break if loop_count >= maxloop
226
-
227
- loop_count += 1
228
- end
229
- end
230
- end
231
-
232
- def select_next_host(pool_name, target, architecture = nil)
233
- datacenter = get_target_datacenter_from_config(pool_name)
234
- cluster = get_target_cluster_from_config(pool_name)
235
- raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
236
- raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
237
-
238
- dc = "#{datacenter}_#{cluster}"
239
- @provider_hosts_lock.synchronize do
240
- if architecture
241
- raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('architectures')
242
-
243
- host = target[dc]['architectures'][architecture].shift
244
- target[dc]['architectures'][architecture] << host
245
- if target[dc]['hosts'].include?(host)
246
- target[dc]['hosts'].delete(host)
247
- target[dc]['hosts'] << host
248
- end
249
- else
250
- raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('hosts')
251
-
252
- host = target[dc]['hosts'].shift
253
- target[dc]['hosts'] << host
254
- target[dc]['architectures'].each do |arch|
255
- target[dc]['architectures'][arch] = arch.partition { |v| v != host }.flatten if arch.include?(host)
256
- end
257
- end
258
-
259
- return host
260
- end
261
- end
262
-
263
- def vm_in_target?(pool_name, parent_host, architecture, target)
264
- datacenter = get_target_datacenter_from_config(pool_name)
265
- cluster = get_target_cluster_from_config(pool_name)
266
- raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
267
- raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
268
-
269
- dc = "#{datacenter}_#{cluster}"
270
- raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory") unless target[dc].key?('hosts')
271
- return true if target[dc]['hosts'].include?(parent_host)
272
- return true if target[dc]['architectures'][architecture].include?(parent_host)
273
-
274
- false
275
- end
276
-
277
- def get_vm(pool_name, vm_name)
278
- vm_hash = nil
279
- @connection_pool.with_metrics do |pool_object|
280
- connection = ensured_vsphere_connection(pool_object)
281
- vm_object = find_vm(pool_name, vm_name, connection)
282
- return vm_hash if vm_object.nil?
283
-
284
- vm_hash = generate_vm_hash(vm_object, pool_name)
285
- end
286
- vm_hash
287
- end
288
-
289
- def create_vm(pool_name, new_vmname)
290
- pool = pool_config(pool_name)
291
- raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
292
-
293
- vm_hash = nil
294
- @connection_pool.with_metrics do |pool_object|
295
- connection = ensured_vsphere_connection(pool_object)
296
- # Assume all pool config is valid i.e. not missing
297
- template_path = pool['template']
298
- target_folder_path = pool['folder']
299
- target_datastore = pool['datastore']
300
- target_datacenter_name = get_target_datacenter_from_config(pool_name)
301
-
302
- # Get the template VM object
303
- raise("Pool #{pool_name} did not specify a full path for the template for the provider #{name}") unless valid_template_path? template_path
304
-
305
- template_vm_object = find_template_vm(pool, connection)
306
-
307
- extra_config = [
308
- { key: 'guestinfo.hostname', value: new_vmname }
309
- ]
310
-
311
- if pool.key?('snapshot_mainMem_ioBlockPages')
312
- ioblockpages = pool['snapshot_mainMem_ioBlockPages']
313
- extra_config.push(
314
- { key: 'mainMem.ioBlockPages', value: ioblockpages }
315
- )
316
- end
317
- if pool.key?('snapshot_mainMem_iowait')
318
- iowait = pool['snapshot_mainMem_iowait']
319
- extra_config.push(
320
- { key: 'mainMem.iowait', value: iowait }
321
- )
322
- end
323
-
324
- # Annotate with creation time, origin template, etc.
325
- # Add extraconfig options that can be queried by vmtools
326
- config_spec = create_config_spec(new_vmname, template_path, extra_config)
327
-
328
- # Check if alternate network configuration is specified and add configuration
329
- if pool.key?('network')
330
- template_vm_network_device = template_vm_object.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first
331
- network_name = pool['network']
332
- network_device = set_network_device(target_datacenter_name, template_vm_network_device, network_name, connection)
333
- config_spec.deviceChange = [{ operation: 'edit', device: network_device }]
334
- end
335
-
336
- # Put the VM in the specified folder and resource pool
337
- relocate_spec = create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection)
338
-
339
- # Create a clone spec
340
- clone_spec = create_clone_spec(relocate_spec, config_spec)
341
-
342
- begin
343
- vm_target_folder = find_vm_folder(pool_name, connection)
344
- vm_target_folder ||= create_folder(connection, target_folder_path, target_datacenter_name) if @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true)
345
- rescue StandardError
346
- if @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true)
347
- vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name)
348
- else
349
- raise
350
- end
351
- end
352
- raise ArgumentError, "Cannot find the configured folder for #{pool_name} #{target_folder_path}" unless vm_target_folder
353
-
354
- # Create the new VM
355
- new_vm_object = template_vm_object.CloneVM_Task(
356
- folder: vm_target_folder,
357
- name: new_vmname,
358
- spec: clone_spec
359
- ).wait_for_completion
360
-
361
- vm_hash = generate_vm_hash(new_vm_object, pool_name)
362
- end
363
- vm_hash
364
- end
365
-
366
- def create_config_spec(vm_name, template_name, extra_config)
367
- RbVmomi::VIM.VirtualMachineConfigSpec(
368
- annotation: JSON.pretty_generate(
369
- name: vm_name,
370
- created_by: provider_config['username'],
371
- base_template: template_name,
372
- creation_timestamp: Time.now.utc
373
- ),
374
- extraConfig: extra_config
375
- )
376
- end
377
-
378
- def create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection)
379
- pool = pool_config(pool_name)
380
- target_cluster_name = get_target_cluster_from_config(pool_name)
381
-
382
- relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(
383
- datastore: find_datastore(target_datastore, connection, target_datacenter_name),
384
- diskMoveType: get_disk_backing(pool)
385
- )
386
- manage_host_selection = @config[:config]['manage_host_selection'] if @config[:config].key?('manage_host_selection')
387
- if manage_host_selection
388
- run_select_hosts(pool_name, @provider_hosts)
389
- target_host = select_next_host(pool_name, @provider_hosts)
390
- host_object = find_host_by_dnsname(connection, target_host)
391
- relocate_spec.host = host_object
392
- else
393
- # Choose a cluster/host to place the new VM on
394
- target_cluster_object = find_cluster(target_cluster_name, connection, target_datacenter_name)
395
- relocate_spec.pool = target_cluster_object.resourcePool
396
- end
397
- relocate_spec
398
- end
399
-
400
- def create_clone_spec(relocate_spec, config_spec)
401
- RbVmomi::VIM.VirtualMachineCloneSpec(
402
- location: relocate_spec,
403
- config: config_spec,
404
- powerOn: true,
405
- template: false
406
- )
407
- end
408
-
409
- def set_network_device(datacenter_name, template_vm_network_device, network_name, connection)
410
- # Retrieve network object
411
- datacenter = connection.serviceInstance.find_datacenter(datacenter_name)
412
- new_network = datacenter.network.find { |n| n.name == network_name }
413
-
414
- raise("Cannot find network #{network_name} in datacenter #{datacenter_name}") unless new_network
415
-
416
- # Determine network device type
417
- # All possible device type options here: https://vdc-download.vmware.com/vmwb-repository/dcr-public/98d63b35-d822-47fe-a87a-ddefd469df06/2e3c7b58-f2bd-486e-8bb1-a75eb0640bee/doc/vim.vm.device.VirtualEthernetCard.html
418
- network_device =
419
- if template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet2
420
- RbVmomi::VIM.VirtualVmxnet2
421
- elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet3
422
- RbVmomi::VIM.VirtualVmxnet3
423
- elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000
424
- RbVmomi::VIM.VirtualE1000
425
- elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000e
426
- RbVmomi::VIM.VirtualE1000e
427
- elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualSriovEthernetCard
428
- RbVmomi::VIM.VirtualSriovEthernetCard
429
- else
430
- RbVmomi::VIM.VirtualPCNet32
431
- end
432
-
433
- # Set up new network device attributes
434
- network_device.key = template_vm_network_device.key
435
- network_device.deviceInfo = RbVmomi::VIM.Description(
436
- label: template_vm_network_device.deviceInfo.label,
437
- summary: network_name
438
- )
439
- network_device.backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(
440
- deviceName: network_name,
441
- network: new_network,
442
- useAutoDetect: false
443
- )
444
- network_device.addressType = 'assigned'
445
- network_device.connectable = RbVmomi::VIM.VirtualDeviceConnectInfo(
446
- allowGuestControl: true,
447
- startConnected: true,
448
- connected: true
449
- )
450
- network_device
451
- end
452
-
453
- def create_disk(pool_name, vm_name, disk_size)
454
- pool = pool_config(pool_name)
455
- raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
456
-
457
- datastore_name = pool['datastore']
458
- raise("Pool #{pool_name} does not have a datastore defined for the provider #{name}") if datastore_name.nil?
459
-
460
- @connection_pool.with_metrics do |pool_object|
461
- connection = ensured_vsphere_connection(pool_object)
462
- vm_object = find_vm(pool_name, vm_name, connection)
463
- raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil?
464
-
465
- add_disk(vm_object, disk_size, datastore_name, connection, get_target_datacenter_from_config(pool_name))
466
- end
467
- true
468
- end
469
-
470
- def create_snapshot(pool_name, vm_name, new_snapshot_name)
471
- @connection_pool.with_metrics do |pool_object|
472
- connection = ensured_vsphere_connection(pool_object)
473
- vm_object = find_vm(pool_name, vm_name, connection)
474
- raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil?
475
-
476
- old_snap = find_snapshot(vm_object, new_snapshot_name)
477
- raise("Snapshot #{new_snapshot_name} for VM #{vm_name} in pool #{pool_name} already exists for the provider #{name}") unless old_snap.nil?
478
-
479
- vm_object.CreateSnapshot_Task(
480
- name: new_snapshot_name,
481
- description: 'vmpooler',
482
- memory: true,
483
- quiesce: true
484
- ).wait_for_completion
485
- end
486
- true
487
- end
488
-
489
- def revert_snapshot(pool_name, vm_name, snapshot_name)
490
- @connection_pool.with_metrics do |pool_object|
491
- connection = ensured_vsphere_connection(pool_object)
492
- vm_object = find_vm(pool_name, vm_name, connection)
493
- raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil?
494
-
495
- snapshot_object = find_snapshot(vm_object, snapshot_name)
496
- raise("Snapshot #{snapshot_name} for VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if snapshot_object.nil?
497
-
498
- snapshot_object.RevertToSnapshot_Task.wait_for_completion
499
- end
500
- true
501
- end
502
-
503
- def destroy_vm(pool_name, vm_name)
504
- @connection_pool.with_metrics do |pool_object|
505
- connection = ensured_vsphere_connection(pool_object)
506
- vm_object = find_vm(pool_name, vm_name, connection)
507
- # If a VM doesn't exist then it is effectively deleted
508
- return true if vm_object.nil?
509
-
510
- # Poweroff the VM if it's running
511
- vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn'
512
-
513
- # Kill it with fire
514
- vm_object.Destroy_Task.wait_for_completion
515
- end
516
- true
517
- end
518
-
519
- def vm_ready?(_pool_name, vm_name)
520
- begin
521
- open_socket(vm_name, global_config[:config]['domain'])
522
- rescue StandardError => _e
523
- return false
524
- end
525
-
526
- true
527
- end
528
-
529
- # VSphere Helper methods
530
-
531
- def get_target_cluster_from_config(pool_name)
532
- pool = pool_config(pool_name)
533
- return nil if pool.nil?
534
-
535
- return pool['clone_target'] unless pool['clone_target'].nil?
536
- return global_config[:config]['clone_target'] unless global_config[:config]['clone_target'].nil?
537
-
538
- nil
539
- end
540
-
541
- def get_target_datacenter_from_config(pool_name)
542
- pool = pool_config(pool_name)
543
- return nil if pool.nil?
544
-
545
- return pool['datacenter'] unless pool['datacenter'].nil?
546
- return provider_config['datacenter'] unless provider_config['datacenter'].nil?
547
-
548
- nil
549
- end
550
-
551
- # Return a hash of VM data
552
- # Provides vmname, hostname, template, poolname, boottime and powerstate information
553
- def generate_vm_hash(vm_object, pool_name)
554
- pool_configuration = pool_config(pool_name)
555
- return nil if pool_configuration.nil?
556
-
557
- hostname = vm_object.summary.guest.hostName if vm_object.summary&.guest && vm_object.summary.guest.hostName
558
- boottime = vm_object.runtime.bootTime if vm_object.runtime&.bootTime
559
- powerstate = vm_object.runtime.powerState if vm_object.runtime&.powerState
560
-
561
- {
562
- 'name' => vm_object.name,
563
- 'hostname' => hostname,
564
- 'template' => pool_configuration['template'],
565
- 'poolname' => pool_name,
566
- 'boottime' => boottime,
567
- 'powerstate' => powerstate
568
- }
569
- end
570
-
571
- # vSphere helper methods
572
- ADAPTER_TYPE = 'lsiLogic'
573
- DISK_TYPE = 'thin'
574
- DISK_MODE = 'persistent'
575
-
576
- def ensured_vsphere_connection(connection_pool_object)
577
- connection_pool_object[:connection] = connect_to_vsphere unless vsphere_connection_ok?(connection_pool_object[:connection])
578
- connection_pool_object[:connection]
579
- end
580
-
581
- def vsphere_connection_ok?(connection)
582
- _result = connection.serviceInstance.CurrentTime
583
- true
584
- rescue StandardError
585
- false
586
- end
587
-
588
- def connect_to_vsphere
589
- max_tries = global_config[:config]['max_tries'] || 3
590
- retry_factor = global_config[:config]['retry_factor'] || 10
591
- try = 1
592
- begin
593
- connection = RbVmomi::VIM.connect host: provider_config['server'],
594
- user: provider_config['username'],
595
- password: provider_config['password'],
596
- insecure: provider_config['insecure'] || false
597
- metrics.increment('connect.open')
598
- connection
599
- rescue StandardError => e
600
- metrics.increment('connect.fail')
601
- raise e if try >= max_tries
602
-
603
- sleep(try * retry_factor)
604
- try += 1
605
- retry
606
- end
607
- end
608
-
609
- # This should supercede the open_socket method in the Pool Manager
610
- def open_socket(host, domain = nil, timeout = 5, port = 22, &_block)
611
- Timeout.timeout(timeout) do
612
- target_host = host
613
- target_host = "#{host}.#{domain}" if domain
614
- sock = TCPSocket.new target_host, port
615
- begin
616
- yield sock if block_given?
617
- ensure
618
- sock.close
619
- end
620
- end
621
- end
622
-
623
- def get_vm_folder_path(vm_object)
624
- # This gives an array starting from the root Datacenters folder all the way to the VM
625
- # [ [Object, String], [Object, String ] ... ]
626
- # It's then reversed so that it now goes from the VM to the Datacenter
627
- full_path = vm_object.path.reverse
628
-
629
- # Find the Datacenter object
630
- dc_index = full_path.index { |p| p[0].is_a?(RbVmomi::VIM::Datacenter) }
631
- return nil if dc_index.nil?
632
- # The Datacenter should be at least 2 otherwise there's something
633
- # wrong with the array passed in
634
- # This is the minimum:
635
- # [ VM (0), VM ROOT FOLDER (1), DC (2)]
636
- return nil if dc_index <= 1
637
-
638
- # Remove the VM name (Starting position of 1 in the slice)
639
- # Up until the Root VM Folder of DataCenter Node (dc_index - 2)
640
- full_path = full_path.slice(1..dc_index - 2)
641
-
642
- # Reverse the array back to normal and
643
- # then convert the array of paths into a '/' seperated string
644
- (full_path.reverse.map { |p| p[1] }).join('/')
645
- end
646
-
647
- def add_disk(vm, size, datastore, connection, datacentername)
648
- return false unless size.to_i > 0
649
-
650
- vmdk_datastore = find_datastore(datastore, connection, datacentername)
651
- raise("Datastore '#{datastore}' does not exist in datacenter '#{datacentername}'") if vmdk_datastore.nil?
652
-
653
- datacenter = connection.serviceInstance.find_datacenter(datacentername)
654
- controller = find_disk_controller(vm)
655
- disk_unit_number = find_disk_unit_number(vm, controller)
656
- disk_count = vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk).count
657
- vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{disk_count}.vmdk"
658
-
659
- vmdk_spec = RbVmomi::VIM::FileBackedVirtualDiskSpec(
660
- capacityKb: size.to_i * 1024 * 1024,
661
- adapterType: ADAPTER_TYPE,
662
- diskType: DISK_TYPE
663
- )
664
-
665
- vmdk_backing = RbVmomi::VIM::VirtualDiskFlatVer2BackingInfo(
666
- datastore: vmdk_datastore,
667
- diskMode: DISK_MODE,
668
- fileName: "[#{datastore}] #{vmdk_file_name}"
669
- )
670
-
671
- device = RbVmomi::VIM::VirtualDisk(
672
- backing: vmdk_backing,
673
- capacityInKB: size.to_i * 1024 * 1024,
674
- controllerKey: controller.key,
675
- key: -1,
676
- unitNumber: disk_unit_number
677
- )
678
-
679
- device_config_spec = RbVmomi::VIM::VirtualDeviceConfigSpec(
680
- device: device,
681
- operation: RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add')
682
- )
683
-
684
- vm_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec(
685
- deviceChange: [device_config_spec]
686
- )
687
-
688
- connection.serviceContent.virtualDiskManager.CreateVirtualDisk_Task(
689
- datacenter: datacenter,
690
- name: "[#{datastore}] #{vmdk_file_name}",
691
- spec: vmdk_spec
692
- ).wait_for_completion
693
-
694
- vm.ReconfigVM_Task(spec: vm_config_spec).wait_for_completion
695
-
696
- true
697
- end
698
-
699
- def find_datastore(datastorename, connection, datacentername)
700
- datacenter = connection.serviceInstance.find_datacenter(datacentername)
701
- raise("Datacenter #{datacentername} does not exist") if datacenter.nil?
702
-
703
- datacenter.find_datastore(datastorename)
704
- end
705
-
706
- def find_device(vm, device_name)
707
- vm.config.hardware.device.each do |device|
708
- return device if device.deviceInfo.label == device_name
709
- end
710
-
711
- nil
712
- end
713
-
714
- def find_disk_controller(vm)
715
- devices = find_disk_devices(vm)
716
-
717
- devices.keys.sort.each do |device|
718
- return find_device(vm, devices[device]['device'].deviceInfo.label) if devices[device]['children'].length < 15
719
- end
720
-
721
- nil
722
- end
723
-
724
- def find_disk_devices(vm)
725
- devices = {}
726
-
727
- vm.config.hardware.device.each do |device|
728
- if device.is_a? RbVmomi::VIM::VirtualSCSIController
729
- if devices[device.controllerKey].nil?
730
- devices[device.key] = {}
731
- devices[device.key]['children'] = []
732
- end
733
-
734
- devices[device.key]['device'] = device
735
- end
736
-
737
- if device.is_a? RbVmomi::VIM::VirtualDisk
738
- if devices[device.controllerKey].nil?
739
- devices[device.controllerKey] = {}
740
- devices[device.controllerKey]['children'] = []
741
- end
742
-
743
- devices[device.controllerKey]['children'].push(device)
744
- end
745
- end
746
-
747
- devices
748
- end
749
-
750
- def find_disk_unit_number(vm, controller)
751
- used_unit_numbers = []
752
- available_unit_numbers = []
753
-
754
- devices = find_disk_devices(vm)
755
-
756
- devices.keys.sort.each do |c|
757
- next unless controller.key == devices[c]['device'].key
758
-
759
- used_unit_numbers.push(devices[c]['device'].scsiCtlrUnitNumber)
760
- devices[c]['children'].each do |disk|
761
- used_unit_numbers.push(disk.unitNumber)
762
- end
763
- end
764
-
765
- (0..15).each do |scsi_id|
766
- available_unit_numbers.push(scsi_id) if used_unit_numbers.grep(scsi_id).length <= 0
767
- end
768
-
769
- available_unit_numbers.min
770
- end
771
-
772
- # Finds a folder object by inventory path
773
- # Params:
774
- # +pool_name+:: the pool to find the folder for
775
- # +connection+:: the vsphere connection object
776
- # returns a ManagedObjectReference for the folder found or nil if not found
777
- def find_vm_folder(pool_name, connection)
778
- # Find a folder by its inventory path and return the object
779
- # Returns nil when the object found is not a folder
780
- pool_configuration = pool_config(pool_name)
781
- return nil if pool_configuration.nil?
782
-
783
- folder = pool_configuration['folder']
784
- datacenter = get_target_datacenter_from_config(pool_name)
785
- return nil if datacenter.nil?
786
-
787
- propSpecs = { # rubocop:disable Naming/VariableName
788
- entity: self,
789
- inventoryPath: "#{datacenter}/vm/#{folder}"
790
- }
791
-
792
- folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
793
- return nil unless folder_object.instance_of? RbVmomi::VIM::Folder
794
-
795
- folder_object
796
- end
797
-
798
- # Returns an array containing cumulative CPU and memory utilization of a host, and its object reference
799
- # Params:
800
- # +model+:: CPU arch version to match on
801
- # +limit+:: Hard limit for CPU or memory utilization beyond which a host is excluded for deployments
802
- # returns nil if one on these conditions is true:
803
- # the model param is defined and cannot be found
804
- # the host is in maintenance mode
805
- # the host status is not 'green'
806
- # the cpu or memory utilization is bigger than the limit param
807
- def get_host_utilization(host, model = nil, limit = 90)
808
- limit = @config[:config]['utilization_limit'] if @config[:config].key?('utilization_limit')
809
- return nil if model && !host_has_cpu_model?(host, model)
810
- return nil if host.runtime.inMaintenanceMode
811
- return nil unless host.overallStatus == 'green'
812
- return nil unless host.configIssue.empty?
813
-
814
- cpu_utilization = cpu_utilization_for host
815
- memory_utilization = memory_utilization_for host
816
-
817
- return nil if cpu_utilization.nil?
818
- return nil if cpu_utilization.to_d == 0.0.to_d
819
- return nil if memory_utilization.nil?
820
- return nil if memory_utilization.to_d == 0.0.to_d
821
-
822
- return nil if cpu_utilization > limit
823
- return nil if memory_utilization > limit
824
-
825
- [cpu_utilization, host]
826
- end
827
-
828
- def host_has_cpu_model?(host, model)
829
- get_host_cpu_arch_version(host) == model
830
- end
831
-
832
- def get_host_cpu_arch_version(host)
833
- cpu_model = host.hardware.cpuPkg[0].description
834
- cpu_model_parts = cpu_model.split
835
- cpu_model_parts[4]
836
- end
837
-
838
- def cpu_utilization_for(host)
839
- cpu_usage = host.summary.quickStats.overallCpuUsage
840
- return nil if cpu_usage.nil?
841
-
842
- cpu_size = host.summary.hardware.cpuMhz * host.summary.hardware.numCpuCores
843
- cpu_usage.fdiv(cpu_size) * 100
844
- end
845
-
846
- def memory_utilization_for(host)
847
- memory_usage = host.summary.quickStats.overallMemoryUsage
848
- return nil if memory_usage.nil?
849
-
850
- memory_size = host.summary.hardware.memorySize / 1024 / 1024
851
- memory_usage.fdiv(memory_size) * 100
852
- end
853
-
854
- def get_average_cluster_utilization(hosts)
855
- utilization_counts = hosts.map { |host| host[0] }
856
- utilization_counts.inject(:+) / hosts.count
857
- end
858
-
859
- def build_compatible_hosts_lists(hosts, percentage)
860
- hosts_with_arch_versions = hosts.map do |h|
861
- {
862
- 'utilization' => h[0],
863
- 'host_object' => h[1],
864
- 'architecture' => get_host_cpu_arch_version(h[1])
865
- }
866
- end
867
- versions = hosts_with_arch_versions.map { |host| host['architecture'] }.uniq
868
- architectures = {}
869
- versions.each do |version|
870
- architectures[version] = []
871
- end
872
-
873
- hosts_with_arch_versions.each do |h|
874
- architectures[h['architecture']] << [h['utilization'], h['host_object'], h['architecture']]
875
- end
876
-
877
- versions.each do |version|
878
- targets = select_least_used_hosts(architectures[version], percentage)
879
- architectures[version] = targets
880
- end
881
- architectures
882
- end
883
-
884
- def select_least_used_hosts(hosts, percentage)
885
- raise('Provided hosts list to select_least_used_hosts is empty') if hosts.empty?
886
-
887
- average_utilization = get_average_cluster_utilization(hosts)
888
- least_used_hosts = []
889
- hosts.each do |host|
890
- least_used_hosts << host if host[0] <= average_utilization
891
- end
892
- hosts_to_select = (hosts.count * (percentage / 100.0)).to_int
893
- hosts_to_select = hosts.count - 1 if percentage == 100
894
- least_used_hosts.sort[0..hosts_to_select].map { |host| host[1].name }
895
- end
896
-
897
- def find_least_used_hosts(cluster, datacentername, percentage)
898
- @connection_pool.with_metrics do |pool_object|
899
- connection = ensured_vsphere_connection(pool_object)
900
- cluster_object = find_cluster(cluster, connection, datacentername)
901
- raise("Cluster #{cluster} cannot be found") if cluster_object.nil?
902
-
903
- target_hosts = get_cluster_host_utilization(cluster_object)
904
- raise("there is no candidate in vcenter that meets all the required conditions, that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty?
905
-
906
- architectures = build_compatible_hosts_lists(target_hosts, percentage)
907
- least_used_hosts = select_least_used_hosts(target_hosts, percentage)
908
- {
909
- 'hosts' => least_used_hosts,
910
- 'architectures' => architectures
911
- }
912
- end
913
- end
914
-
915
- def find_host_by_dnsname(connection, dnsname)
916
- host_object = connection.searchIndex.FindByDnsName(dnsName: dnsname, vmSearch: false)
917
- return nil if host_object.nil?
918
-
919
- host_object
920
- end
921
-
922
- def find_least_used_host(cluster, connection, datacentername)
923
- cluster_object = find_cluster(cluster, connection, datacentername)
924
- target_hosts = get_cluster_host_utilization(cluster_object)
925
- raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty?
926
-
927
- target_hosts.min[1]
928
- end
929
-
930
- def find_cluster(cluster, connection, datacentername)
931
- datacenter = connection.serviceInstance.find_datacenter(datacentername)
932
- raise("Datacenter #{datacentername} does not exist") if datacenter.nil?
933
-
934
- # In the event the cluster is not a direct descendent of the
935
- # datacenter, we use a ContainerView to leverage its recursive
936
- # search. This will find clusters which are, for example, in
937
- # folders under the datacenter. This will also find standalone
938
- # hosts which are not part of a cluster.
939
- cv = connection.serviceContent.viewManager.CreateContainerView(
940
- container: datacenter.hostFolder,
941
- type: ['ComputeResource', 'ClusterComputeResource'],
942
- recursive: true
943
- )
944
- cluster = cv.view.find { |cluster_object| cluster_object.name == cluster }
945
- cv.DestroyView
946
- cluster
947
- end
948
-
949
- def get_cluster_host_utilization(cluster, model = nil)
950
- cluster_hosts = []
951
- cluster.host.each do |host|
952
- host_usage = get_host_utilization(host, model)
953
- cluster_hosts << host_usage if host_usage
954
- end
955
- cluster_hosts
956
- end
957
-
958
- def find_least_used_vpshere_compatible_host(vm)
959
- source_host = vm.summary.runtime.host
960
- model = get_host_cpu_arch_version(source_host)
961
- cluster = source_host.parent
962
- target_hosts = get_cluster_host_utilization(cluster, model)
963
- raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty?
964
-
965
- target_host = target_hosts.min[1]
966
- [target_host, target_host.name]
967
- end
968
-
969
- def find_snapshot(vm, snapshotname)
970
- get_snapshot_list(vm.snapshot.rootSnapshotList, snapshotname) if vm.snapshot
971
- end
972
-
973
- def build_propSpecs(datacenter, folder, vmname) # rubocop:disable Naming/MethodName
974
- {
975
- entity => self,
976
- :inventoryPath => "#{datacenter}/vm/#{folder}/#{vmname}"
977
- }
978
- end
979
-
980
- def find_vm(pool_name, vmname, connection)
981
- # Find a VM by its inventory path and return the VM object
982
- # Returns nil when a VM, or pool configuration, cannot be found
983
- pool_configuration = pool_config(pool_name)
984
- return nil if pool_configuration.nil?
985
-
986
- folder = pool_configuration['folder']
987
- datacenter = get_target_datacenter_from_config(pool_name)
988
- return nil if datacenter.nil?
989
-
990
- propSpecs = { # rubocop:disable Naming/VariableName
991
- entity: self,
992
- inventoryPath: "#{datacenter}/vm/#{folder}/#{vmname}"
993
- }
994
-
995
- connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
996
- end
997
-
998
- def get_base_vm_container_from(connection)
999
- view_manager = connection.serviceContent.viewManager
1000
- view_manager.CreateContainerView(
1001
- container: connection.serviceContent.rootFolder,
1002
- recursive: true,
1003
- type: ['VirtualMachine']
1004
- )
1005
- end
1006
-
1007
- def get_snapshot_list(tree, snapshotname)
1008
- snapshot = nil
1009
-
1010
- tree.each do |child|
1011
- if child.name == snapshotname
1012
- snapshot ||= child.snapshot
1013
- else
1014
- snapshot ||= get_snapshot_list(child.childSnapshotList, snapshotname)
1015
- end
1016
- end
1017
-
1018
- snapshot
1019
- end
1020
-
1021
- def get_vm_details(pool_name, vm_name, connection)
1022
- vm_object = find_vm(pool_name, vm_name, connection)
1023
- return nil if vm_object.nil?
1024
-
1025
- parent_host_object = vm_object.summary.runtime.host if vm_object.summary&.runtime && vm_object.summary.runtime.host
1026
- raise('Unable to determine which host the VM is running on') if parent_host_object.nil?
1027
-
1028
- parent_host = parent_host_object.name
1029
- architecture = get_host_cpu_arch_version(parent_host_object)
1030
- {
1031
- 'host_name' => parent_host,
1032
- 'object' => vm_object,
1033
- 'architecture' => architecture
1034
- }
1035
- end
1036
-
1037
- def migration_enabled?(config)
1038
- migration_limit = config[:config]['migration_limit']
1039
- return false unless migration_limit.is_a? Integer
1040
- return true if migration_limit > 0
1041
-
1042
- false
1043
- end
1044
-
1045
- def migrate_vm(pool_name, vm_name)
1046
- @connection_pool.with_metrics do |pool_object|
1047
- begin
1048
- connection = ensured_vsphere_connection(pool_object)
1049
- vm_hash = get_vm_details(pool_name, vm_name, connection)
1050
- @redis.with_metrics do |redis|
1051
- redis.hset("vmpooler__vm__#{vm_name}", 'host', vm_hash['host_name'])
1052
- migration_count = redis.scard('vmpooler__migration')
1053
- migration_limit = @config[:config]['migration_limit'] if @config[:config].key?('migration_limit')
1054
- if migration_enabled? @config
1055
- if migration_count >= migration_limit
1056
- logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}. No migration will be evaluated since the migration_limit has been reached")
1057
- break
1058
- end
1059
- run_select_hosts(pool_name, @provider_hosts)
1060
- if vm_in_target?(pool_name, vm_hash['host_name'], vm_hash['architecture'], @provider_hosts)
1061
- logger.log('s', "[ ] [#{pool_name}] No migration required for '#{vm_name}' running on #{vm_hash['host_name']}")
1062
- else
1063
- migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection)
1064
- end
1065
- else
1066
- logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
1067
- end
1068
- end
1069
- rescue StandardError
1070
- logger.log('s', "[!] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
1071
- raise
1072
- end
1073
- end
1074
- end
1075
-
1076
- def migrate_vm_to_new_host(pool_name, vm_name, vm_hash, connection)
1077
- @redis.with_metrics do |redis|
1078
- redis.sadd('vmpooler__migration', vm_name)
1079
- end
1080
- target_host_name = select_next_host(pool_name, @provider_hosts, vm_hash['architecture'])
1081
- target_host_object = find_host_by_dnsname(connection, target_host_name)
1082
- finish = migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, target_host_name)
1083
- @redis.with_metrics do |redis|
1084
- redis.multi
1085
- redis.hset("vmpooler__vm__#{vm_name}", 'host', target_host_name)
1086
- redis.hset("vmpooler__vm__#{vm_name}", 'migrated', true)
1087
- redis.exec
1088
- end
1089
- logger.log('s', "[>] [#{pool_name}] '#{vm_name}' migrated from #{vm_hash['host_name']} to #{target_host_name} in #{finish} seconds")
1090
- ensure
1091
- @redis.with_metrics do |redis|
1092
- redis.srem('vmpooler__migration', vm_name)
1093
- end
1094
- end
1095
-
1096
- def migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, dest_host_name)
1097
- start = Time.now
1098
- migrate_vm_host(vm_hash['object'], target_host_object)
1099
- finish = format('%<time>.2f', time: Time.now - start)
1100
- metrics.timing("migrate.#{pool_name}", finish)
1101
- metrics.increment("migrate_from.#{vm_hash['host_name']}")
1102
- metrics.increment("migrate_to.#{dest_host_name}")
1103
- @redis.with_metrics do |redis|
1104
- checkout_to_migration = format('%<time>.2f', time: Time.now - Time.parse(redis.hget("vmpooler__vm__#{vm_name}", 'checkout')))
1105
- redis.multi
1106
- redis.hset("vmpooler__vm__#{vm_name}", 'migration_time', finish)
1107
- redis.hset("vmpooler__vm__#{vm_name}", 'checkout_to_migration', checkout_to_migration)
1108
- redis.exec
1109
- end
1110
- finish
1111
- end
1112
-
1113
- def migrate_vm_host(vm_object, host)
1114
- relospec = RbVmomi::VIM.VirtualMachineRelocateSpec(host: host)
1115
- vm_object.RelocateVM_Task(spec: relospec).wait_for_completion
1116
- end
1117
-
1118
- def create_folder(connection, new_folder, datacenter)
1119
- dc = connection.serviceInstance.find_datacenter(datacenter)
1120
- folder_object = dc.vmFolder.traverse(new_folder, RbVmomi::VIM::Folder, true)
1121
- raise("Cannot create folder #{new_folder}") if folder_object.nil?
1122
-
1123
- folder_object
1124
- end
1125
-
1126
- def find_template_vm(pool, connection)
1127
- datacenter = get_target_datacenter_from_config(pool['name'])
1128
- raise('cannot find datacenter') if datacenter.nil?
1129
-
1130
- propSpecs = { # rubocop:disable Naming/VariableName
1131
- entity: self,
1132
- inventoryPath: "#{datacenter}/vm/#{pool['template']}"
1133
- }
1134
-
1135
- template_vm_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
1136
- raise("Pool #{pool['name']} specifies a template VM of #{pool['template']} which does not exist for the provider #{name}") if template_vm_object.nil?
1137
-
1138
- template_vm_object
1139
- end
1140
-
1141
- def create_template_delta_disks(pool)
1142
- @connection_pool.with_metrics do |pool_object|
1143
- connection = ensured_vsphere_connection(pool_object)
1144
- template_vm_object = find_template_vm(pool, connection)
1145
-
1146
- template_vm_object.add_delta_disk_layer_on_all_disks
1147
- end
1148
- end
1149
-
1150
- def valid_template_path?(template)
1151
- return false unless template.include?('/')
1152
- return false if template[0] == '/'
1153
- return false if template[-1] == '/'
1154
-
1155
- true
1156
- end
1157
-
1158
- def get_disk_backing(pool)
1159
- return :moveChildMostDiskBacking if linked_clone?(pool)
1160
-
1161
- :moveAllDiskBackingsAndConsolidate
1162
- end
1163
-
1164
- def linked_clone?(pool)
1165
- return if pool['create_linked_clone'] == false
1166
- return true if pool['create_linked_clone']
1167
- return true if @config[:config]['create_linked_clones']
1168
- end
1169
- end
1170
- end
1171
- end
1172
- end