vmpooler 0.10.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/vmpooler +2 -3
- data/lib/vmpooler.rb +20 -11
- data/lib/vmpooler/api.rb +2 -0
- data/lib/vmpooler/api/dashboard.rb +12 -5
- data/lib/vmpooler/api/helpers.rb +49 -44
- data/lib/vmpooler/api/reroute.rb +2 -0
- data/lib/vmpooler/api/v1.rb +58 -58
- data/lib/vmpooler/dashboard.rb +2 -1
- data/lib/vmpooler/dummy_statsd.rb +2 -0
- data/lib/vmpooler/generic_connection_pool.rb +8 -6
- data/lib/vmpooler/graphite.rb +5 -5
- data/lib/vmpooler/logger.rb +3 -1
- data/lib/vmpooler/pool_manager.rb +143 -126
- data/lib/vmpooler/providers.rb +13 -15
- data/lib/vmpooler/providers/base.rb +5 -3
- data/lib/vmpooler/providers/dummy.rb +16 -12
- data/lib/vmpooler/providers/vsphere.rb +118 -99
- data/lib/vmpooler/statsd.rb +9 -9
- data/lib/vmpooler/version.rb +3 -1
- metadata +14 -14
data/lib/vmpooler/providers.rb
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathname'
|
2
4
|
|
3
5
|
module Vmpooler
|
4
6
|
class Providers
|
5
|
-
|
6
7
|
# @param names [Array] - an array of names or string name of a provider
|
7
8
|
# @return [Array] - list of provider files loaded
|
8
9
|
# ie. ["lib/vmpooler/providers/base.rb", "lib/vmpooler/providers/dummy.rb", "lib/vmpooler/providers/vsphere.rb"]
|
9
10
|
def self.load_by_name(names)
|
10
11
|
names = Array(names)
|
11
|
-
instance =
|
12
|
-
names.map {|name| instance.load_from_gems(name)}.flatten
|
12
|
+
instance = new
|
13
|
+
names.map { |name| instance.load_from_gems(name) }.flatten
|
13
14
|
end
|
14
15
|
|
15
16
|
# @return [Array] - array of provider files
|
16
17
|
# ie. ["lib/vmpooler/providers/base.rb", "lib/vmpooler/providers/dummy.rb", "lib/vmpooler/providers/vsphere.rb"]
|
17
18
|
# although these files can come from any gem
|
18
19
|
def self.load_all_providers
|
19
|
-
|
20
|
+
new.load_from_gems
|
20
21
|
end
|
21
22
|
|
22
23
|
# @return [Array] - returns an array of gem names that contain a provider
|
23
24
|
def self.installed_providers
|
24
|
-
|
25
|
+
new.vmpooler_provider_gem_list.map(&:name)
|
25
26
|
end
|
26
27
|
|
27
28
|
# @return [Array] returns a list of vmpooler providers gem plugin specs
|
@@ -38,7 +39,7 @@ module Vmpooler
|
|
38
39
|
# we don't exactly know if the provider name matches the main file name that should be loaded
|
39
40
|
# so we use globs to get everything like the name
|
40
41
|
# this could mean that vsphere5 and vsphere6 are loaded when only vsphere5 is used
|
41
|
-
Dir.glob(File.join(gem_path, "*#{name}*.rb")).each do |file|
|
42
|
+
Dir.glob(File.join(gem_path, "*#{name}*.rb")).sort.each do |file|
|
42
43
|
require file
|
43
44
|
end
|
44
45
|
end
|
@@ -50,7 +51,7 @@ module Vmpooler
|
|
50
51
|
# @return [String] - the relative path to the vmpooler provider dir
|
51
52
|
# this is used when searching gems for this path
|
52
53
|
def provider_path
|
53
|
-
File.join('lib','vmpooler','providers')
|
54
|
+
File.join('lib', 'vmpooler', 'providers')
|
54
55
|
end
|
55
56
|
|
56
57
|
# Add constants to array to skip over classes, ie. Vmpooler::PoolManager::Provider::Dummy
|
@@ -81,26 +82,24 @@ module Vmpooler
|
|
81
82
|
@plugin_map ||= Hash[plugin_classes.map { |gem| [gem.send(:name), gem] }]
|
82
83
|
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
85
|
# Internal: Retrieve a list of available gem paths from RubyGems.
|
87
86
|
#
|
88
87
|
# Returns an Array of Pathname objects.
|
89
88
|
def gem_directories
|
90
89
|
dirs = []
|
91
|
-
if
|
90
|
+
if rubygems?
|
92
91
|
dirs = gemspecs.map do |spec|
|
93
|
-
lib_path = File.expand_path(File.join(spec.full_gem_path,provider_path))
|
94
|
-
lib_path if File.
|
92
|
+
lib_path = File.expand_path(File.join(spec.full_gem_path, provider_path))
|
93
|
+
lib_path if File.exist? lib_path
|
95
94
|
end + included_lib_dirs
|
96
95
|
end
|
97
|
-
dirs.reject
|
96
|
+
dirs.reject(&:nil?).uniq
|
98
97
|
end
|
99
98
|
|
100
99
|
# Internal: Check if RubyGems is loaded and available.
|
101
100
|
#
|
102
101
|
# Returns true if RubyGems is available, false if not.
|
103
|
-
def
|
102
|
+
def rubygems?
|
104
103
|
defined? ::Gem
|
105
104
|
end
|
106
105
|
|
@@ -114,6 +113,5 @@ module Vmpooler
|
|
114
113
|
Gem.searcher.init_gemspecs
|
115
114
|
end
|
116
115
|
end
|
117
|
-
|
118
116
|
end
|
119
117
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Vmpooler
|
2
4
|
class PoolManager
|
3
5
|
class Provider
|
@@ -222,7 +224,7 @@ module Vmpooler
|
|
222
224
|
# [Hash] pool : Configuration for the pool
|
223
225
|
# returns
|
224
226
|
# nil when successful. Raises error when encountered
|
225
|
-
def create_template_delta_disks(
|
227
|
+
def create_template_delta_disks(_pool)
|
226
228
|
raise("#{self.class.name} does not implement create_template_delta_disks")
|
227
229
|
end
|
228
230
|
|
@@ -230,11 +232,11 @@ module Vmpooler
|
|
230
232
|
# [String] provider_name : Name of the provider
|
231
233
|
# returns
|
232
234
|
# Hash of folders
|
233
|
-
def get_target_datacenter_from_config(
|
235
|
+
def get_target_datacenter_from_config(_provider_name)
|
234
236
|
raise("#{self.class.name} does not implement get_target_datacenter_from_config")
|
235
237
|
end
|
236
238
|
|
237
|
-
def purge_unconfigured_folders(
|
239
|
+
def purge_unconfigured_folders(_base_folders, _configured_folders, _whitelist)
|
238
240
|
raise("#{self.class.name} does not implement purge_unconfigured_folders")
|
239
241
|
end
|
240
242
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
require 'vmpooler/providers/base'
|
3
5
|
|
@@ -73,10 +75,10 @@ module Vmpooler
|
|
73
75
|
return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil?
|
74
76
|
|
75
77
|
# Only migrate if migratevm_couldmove_percent is met
|
76
|
-
return current_vm['vm_host'] if
|
78
|
+
return current_vm['vm_host'] if rand(1..100) > provider_config['migratevm_couldmove_percent']
|
77
79
|
|
78
80
|
# Simulate a 10 node cluster and randomly pick a different one
|
79
|
-
new_host = 'HOST' + (1
|
81
|
+
new_host = 'HOST' + rand(1..10).to_s while new_host == current_vm['vm_host']
|
80
82
|
|
81
83
|
new_host
|
82
84
|
end
|
@@ -93,7 +95,7 @@ module Vmpooler
|
|
93
95
|
|
94
96
|
# Inject clone failure
|
95
97
|
unless provider_config['migratevm_fail_percent'].nil?
|
96
|
-
raise('Dummy Failure for migratevm_fail_percent') if
|
98
|
+
raise('Dummy Failure for migratevm_fail_percent') if rand(1..100) <= provider_config['migratevm_fail_percent']
|
97
99
|
end
|
98
100
|
|
99
101
|
@write_lock.synchronize do
|
@@ -114,7 +116,7 @@ module Vmpooler
|
|
114
116
|
|
115
117
|
# Randomly power off the VM
|
116
118
|
unless dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?
|
117
|
-
if
|
119
|
+
if rand(1..100) <= provider_config['getvm_poweroff_percent']
|
118
120
|
@write_lock.synchronize do
|
119
121
|
dummy = get_dummy_vm(pool_name, vm_name)
|
120
122
|
dummy['powerstate'] = 'PoweredOff'
|
@@ -126,7 +128,7 @@ module Vmpooler
|
|
126
128
|
|
127
129
|
# Randomly rename the host
|
128
130
|
unless dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?
|
129
|
-
if
|
131
|
+
if rand(1..100) <= provider_config['getvm_rename_percent']
|
130
132
|
@write_lock.synchronize do
|
131
133
|
dummy = get_dummy_vm(pool_name, vm_name)
|
132
134
|
dummy['hostname'] = 'DUMMY' + dummy['name']
|
@@ -194,7 +196,7 @@ module Vmpooler
|
|
194
196
|
begin
|
195
197
|
# Inject clone failure
|
196
198
|
unless provider_config['createvm_fail_percent'].nil?
|
197
|
-
raise('Dummy Failure for createvm_fail_percent') if
|
199
|
+
raise('Dummy Failure for createvm_fail_percent') if rand(1..100) <= provider_config['createvm_fail_percent']
|
198
200
|
end
|
199
201
|
|
200
202
|
# Assert the VM is ready for use
|
@@ -202,7 +204,7 @@ module Vmpooler
|
|
202
204
|
vm['dummy_state'] = 'RUNNING'
|
203
205
|
write_backing_file
|
204
206
|
end
|
205
|
-
rescue =>
|
207
|
+
rescue StandardError => _e
|
206
208
|
@write_lock.synchronize do
|
207
209
|
remove_dummy_vm(pool_name, dummy_hostname)
|
208
210
|
write_backing_file
|
@@ -227,7 +229,7 @@ module Vmpooler
|
|
227
229
|
|
228
230
|
# Inject create failure
|
229
231
|
unless provider_config['createdisk_fail_percent'].nil?
|
230
|
-
raise('Dummy Failure for createdisk_fail_percent') if
|
232
|
+
raise('Dummy Failure for createdisk_fail_percent') if rand(1..100) <= provider_config['createdisk_fail_percent']
|
231
233
|
end
|
232
234
|
|
233
235
|
@write_lock.synchronize do
|
@@ -253,7 +255,7 @@ module Vmpooler
|
|
253
255
|
|
254
256
|
# Inject create failure
|
255
257
|
unless provider_config['createsnapshot_fail_percent'].nil?
|
256
|
-
raise('Dummy Failure for createsnapshot_fail_percent') if
|
258
|
+
raise('Dummy Failure for createsnapshot_fail_percent') if rand(1..100) <= provider_config['createsnapshot_fail_percent']
|
257
259
|
end
|
258
260
|
|
259
261
|
@write_lock.synchronize do
|
@@ -280,7 +282,7 @@ module Vmpooler
|
|
280
282
|
|
281
283
|
# Inject create failure
|
282
284
|
unless provider_config['revertsnapshot_fail_percent'].nil?
|
283
|
-
raise('Dummy Failure for revertsnapshot_fail_percent') if
|
285
|
+
raise('Dummy Failure for revertsnapshot_fail_percent') if rand(1..100) <= provider_config['revertsnapshot_fail_percent']
|
284
286
|
end
|
285
287
|
end
|
286
288
|
|
@@ -318,7 +320,7 @@ module Vmpooler
|
|
318
320
|
|
319
321
|
# Inject destroy VM failure
|
320
322
|
unless provider_config['destroyvm_fail_percent'].nil?
|
321
|
-
raise('Dummy Failure for migratevm_fail_percent') if
|
323
|
+
raise('Dummy Failure for migratevm_fail_percent') if rand(1..100) <= provider_config['destroyvm_fail_percent']
|
322
324
|
end
|
323
325
|
|
324
326
|
# 'Destroy' the VM
|
@@ -352,7 +354,7 @@ module Vmpooler
|
|
352
354
|
sleep(2)
|
353
355
|
|
354
356
|
unless provider_config['vmready_fail_percent'].nil?
|
355
|
-
raise('Dummy Failure for vmready_fail_percent') if
|
357
|
+
raise('Dummy Failure for vmready_fail_percent') if rand(1..100) <= provider_config['vmready_fail_percent']
|
356
358
|
end
|
357
359
|
|
358
360
|
@write_lock.synchronize do
|
@@ -370,6 +372,7 @@ module Vmpooler
|
|
370
372
|
|
371
373
|
def remove_dummy_vm(pool_name, vm_name)
|
372
374
|
return if @dummylist['pool'][pool_name].nil?
|
375
|
+
|
373
376
|
new_poollist = @dummylist['pool'][pool_name].delete_if { |vm| vm['name'] == vm_name }
|
374
377
|
@dummylist['pool'][pool_name] = new_poollist
|
375
378
|
end
|
@@ -395,6 +398,7 @@ module Vmpooler
|
|
395
398
|
def write_backing_file
|
396
399
|
dummyfilename = provider_config['filename']
|
397
400
|
return if dummyfilename.nil?
|
401
|
+
|
398
402
|
File.open(dummyfilename, 'w') { |file| file.write(YAML.dump(@dummylist)) }
|
399
403
|
end
|
400
404
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'vmpooler/providers/base'
|
2
4
|
|
3
5
|
module Vmpooler
|
@@ -50,7 +52,8 @@ module Vmpooler
|
|
50
52
|
end
|
51
53
|
return false unless configured_folders.keys.include?(folder_title)
|
52
54
|
return false unless configured_folders[folder_title] == base_folder
|
53
|
-
|
55
|
+
|
56
|
+
true
|
54
57
|
end
|
55
58
|
|
56
59
|
def destroy_vm_and_log(vm_name, vm_object, pool, data_ttl)
|
@@ -69,17 +72,17 @@ module Vmpooler
|
|
69
72
|
logger.log('s', "[!] [#{pool}] '#{vm_name}' is a folder, bailing on destroying")
|
70
73
|
raise('Expected VM, but received a folder object')
|
71
74
|
end
|
72
|
-
vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime
|
75
|
+
vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn'
|
73
76
|
vm_object.Destroy_Task.wait_for_completion
|
74
77
|
|
75
|
-
finish = format('
|
78
|
+
finish = format('%<time>.2f', time: Time.now - start)
|
76
79
|
logger.log('s', "[-] [#{pool}] '#{vm_name}' destroyed in #{finish} seconds")
|
77
80
|
metrics.timing("destroy.#{pool}", finish)
|
78
81
|
rescue RuntimeError
|
79
82
|
raise
|
80
|
-
rescue =>
|
83
|
+
rescue StandardError => e
|
81
84
|
try += 1
|
82
|
-
logger.log('s', "[!] [#{pool}] failed to destroy '#{vm_name}' with an error: #{
|
85
|
+
logger.log('s', "[!] [#{pool}] failed to destroy '#{vm_name}' with an error: #{e}")
|
83
86
|
try >= max_tries ? raise : retry
|
84
87
|
end
|
85
88
|
|
@@ -104,7 +107,7 @@ module Vmpooler
|
|
104
107
|
max_tries = 3
|
105
108
|
logger.log('s', "[-] [#{folder_object.name}] removing unconfigured folder")
|
106
109
|
folder_object.Destroy_Task.wait_for_completion
|
107
|
-
rescue
|
110
|
+
rescue StandardError
|
108
111
|
try += 1
|
109
112
|
try >= max_tries ? raise : retry
|
110
113
|
end
|
@@ -115,13 +118,11 @@ module Vmpooler
|
|
115
118
|
|
116
119
|
base_folders.each do |base_folder|
|
117
120
|
folder_children = get_folder_children(base_folder, connection)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
end
|
121
|
+
next if folder_children.empty?
|
122
|
+
|
123
|
+
folder_children.each do |folder_hash|
|
124
|
+
folder_hash.each do |folder_title, folder_object|
|
125
|
+
destroy_folder_and_children(folder_object) unless folder_configured?(folder_title, base_folder, configured_folders, whitelist)
|
125
126
|
end
|
126
127
|
end
|
127
128
|
end
|
@@ -131,16 +132,17 @@ module Vmpooler
|
|
131
132
|
def get_folder_children(folder_name, connection)
|
132
133
|
folders = []
|
133
134
|
|
134
|
-
propSpecs = {
|
135
|
-
:
|
136
|
-
:
|
135
|
+
propSpecs = { # rubocop:disable Naming/VariableName
|
136
|
+
entity: self,
|
137
|
+
inventoryPath: folder_name
|
137
138
|
}
|
138
|
-
folder_object = connection.searchIndex.FindByInventoryPath(propSpecs)
|
139
|
+
folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
|
139
140
|
|
140
141
|
return folders if folder_object.nil?
|
141
142
|
|
142
143
|
folder_object.childEntity.each do |folder|
|
143
144
|
next unless folder.is_a? RbVmomi::VIM::Folder
|
145
|
+
|
144
146
|
folders << { folder.name => folder }
|
145
147
|
end
|
146
148
|
|
@@ -171,9 +173,9 @@ module Vmpooler
|
|
171
173
|
target[dc]['checking'] = true
|
172
174
|
hosts_hash = find_least_used_hosts(cluster, datacenter, percentage)
|
173
175
|
target[dc] = hosts_hash
|
174
|
-
rescue
|
176
|
+
rescue StandardError
|
175
177
|
target[dc] = {}
|
176
|
-
raise
|
178
|
+
raise
|
177
179
|
ensure
|
178
180
|
target[dc]['check_time_finished'] = Time.now
|
179
181
|
end
|
@@ -188,36 +190,36 @@ module Vmpooler
|
|
188
190
|
cluster = get_target_cluster_from_config(pool_name)
|
189
191
|
raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
|
190
192
|
raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
|
193
|
+
|
191
194
|
dc = "#{datacenter}_#{cluster}"
|
192
195
|
unless target.key?(dc)
|
193
196
|
select_target_hosts(target, cluster, datacenter)
|
194
197
|
return
|
195
198
|
end
|
196
|
-
if target[dc].key?('checking')
|
197
|
-
wait_for_host_selection(dc, target, loop_delay, max_age)
|
198
|
-
end
|
199
|
+
wait_for_host_selection(dc, target, loop_delay, max_age) if target[dc].key?('checking')
|
199
200
|
if target[dc].key?('check_time_finished')
|
200
|
-
if now - target[dc]['check_time_finished'] > max_age
|
201
|
-
select_target_hosts(target, cluster, datacenter)
|
202
|
-
end
|
201
|
+
select_target_hosts(target, cluster, datacenter) if now - target[dc]['check_time_finished'] > max_age
|
203
202
|
end
|
204
203
|
end
|
205
204
|
|
206
205
|
def wait_for_host_selection(dc, target, maxloop = 0, loop_delay = 1, max_age = 60)
|
207
206
|
loop_count = 1
|
208
|
-
until target.key?(dc)
|
207
|
+
until target.key?(dc) && target[dc].key?('check_time_finished')
|
209
208
|
sleep(loop_delay)
|
210
|
-
unless maxloop
|
209
|
+
unless maxloop == 0
|
211
210
|
break if loop_count >= maxloop
|
211
|
+
|
212
212
|
loop_count += 1
|
213
213
|
end
|
214
214
|
end
|
215
215
|
return unless target[dc].key?('check_time_finished')
|
216
|
+
|
216
217
|
loop_count = 1
|
217
218
|
while Time.now - target[dc]['check_time_finished'] > max_age
|
218
219
|
sleep(loop_delay)
|
219
|
-
unless maxloop
|
220
|
+
unless maxloop == 0
|
220
221
|
break if loop_count >= maxloop
|
222
|
+
|
221
223
|
loop_count += 1
|
222
224
|
end
|
223
225
|
end
|
@@ -228,28 +230,29 @@ module Vmpooler
|
|
228
230
|
cluster = get_target_cluster_from_config(pool_name)
|
229
231
|
raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
|
230
232
|
raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
|
233
|
+
|
231
234
|
dc = "#{datacenter}_#{cluster}"
|
232
235
|
@provider_hosts_lock.synchronize do
|
233
236
|
if architecture
|
234
237
|
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')
|
238
|
+
|
235
239
|
host = target[dc]['architectures'][architecture].shift
|
236
240
|
target[dc]['architectures'][architecture] << host
|
237
241
|
if target[dc]['hosts'].include?(host)
|
238
242
|
target[dc]['hosts'].delete(host)
|
239
243
|
target[dc]['hosts'] << host
|
240
244
|
end
|
241
|
-
return host
|
242
245
|
else
|
243
246
|
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')
|
247
|
+
|
244
248
|
host = target[dc]['hosts'].shift
|
245
249
|
target[dc]['hosts'] << host
|
246
250
|
target[dc]['architectures'].each do |arch|
|
247
|
-
if arch.include?(host)
|
248
|
-
target[dc]['architectures'][arch] = arch.partition { |v| v != host }.flatten
|
249
|
-
end
|
251
|
+
target[dc]['architectures'][arch] = arch.partition { |v| v != host }.flatten if arch.include?(host)
|
250
252
|
end
|
251
|
-
return host
|
252
253
|
end
|
254
|
+
|
255
|
+
return host
|
253
256
|
end
|
254
257
|
end
|
255
258
|
|
@@ -258,11 +261,13 @@ module Vmpooler
|
|
258
261
|
cluster = get_target_cluster_from_config(pool_name)
|
259
262
|
raise("cluster for pool #{pool_name} cannot be identified") if cluster.nil?
|
260
263
|
raise("datacenter for pool #{pool_name} cannot be identified") if datacenter.nil?
|
264
|
+
|
261
265
|
dc = "#{datacenter}_#{cluster}"
|
262
266
|
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')
|
263
267
|
return true if target[dc]['hosts'].include?(parent_host)
|
264
268
|
return true if target[dc]['architectures'][architecture].include?(parent_host)
|
265
|
-
|
269
|
+
|
270
|
+
false
|
266
271
|
end
|
267
272
|
|
268
273
|
def get_vm(pool_name, vm_name)
|
@@ -280,6 +285,7 @@ module Vmpooler
|
|
280
285
|
def create_vm(pool_name, new_vmname)
|
281
286
|
pool = pool_config(pool_name)
|
282
287
|
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
|
288
|
+
|
283
289
|
vm_hash = nil
|
284
290
|
@connection_pool.with_metrics do |pool_object|
|
285
291
|
connection = ensured_vsphere_connection(pool_object)
|
@@ -322,7 +328,7 @@ module Vmpooler
|
|
322
328
|
host_object = find_host_by_dnsname(connection, target_host)
|
323
329
|
relocate_spec.host = host_object
|
324
330
|
else
|
325
|
-
|
331
|
+
# Choose a cluster/host to place the new VM on
|
326
332
|
target_cluster_object = find_cluster(target_cluster_name, connection, target_datacenter_name)
|
327
333
|
relocate_spec.pool = target_cluster_object.resourcePool
|
328
334
|
end
|
@@ -337,14 +343,12 @@ module Vmpooler
|
|
337
343
|
|
338
344
|
begin
|
339
345
|
vm_target_folder = find_vm_folder(pool_name, connection)
|
340
|
-
if vm_target_folder.nil?
|
341
|
-
|
342
|
-
|
343
|
-
rescue => _err
|
344
|
-
if @config[:config].key?('create_folders') and @config[:config]['create_folders'] == true
|
346
|
+
vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name) if vm_target_folder.nil? && @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true)
|
347
|
+
rescue StandardError
|
348
|
+
if @config[:config].key?('create_folders') && (@config[:config]['create_folders'] == true)
|
345
349
|
vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name)
|
346
350
|
else
|
347
|
-
raise
|
351
|
+
raise
|
348
352
|
end
|
349
353
|
end
|
350
354
|
|
@@ -418,7 +422,7 @@ module Vmpooler
|
|
418
422
|
return true if vm_object.nil?
|
419
423
|
|
420
424
|
# Poweroff the VM if it's running
|
421
|
-
vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime
|
425
|
+
vm_object.PowerOffVM_Task.wait_for_completion if vm_object.runtime&.powerState && vm_object.runtime.powerState == 'poweredOn'
|
422
426
|
|
423
427
|
# Kill it with fire
|
424
428
|
vm_object.Destroy_Task.wait_for_completion
|
@@ -429,7 +433,7 @@ module Vmpooler
|
|
429
433
|
def vm_ready?(_pool_name, vm_name)
|
430
434
|
begin
|
431
435
|
open_socket(vm_name, global_config[:config]['domain'])
|
432
|
-
rescue =>
|
436
|
+
rescue StandardError => _e
|
433
437
|
return false
|
434
438
|
end
|
435
439
|
|
@@ -464,9 +468,9 @@ module Vmpooler
|
|
464
468
|
pool_configuration = pool_config(pool_name)
|
465
469
|
return nil if pool_configuration.nil?
|
466
470
|
|
467
|
-
hostname = vm_object.summary.guest.hostName if vm_object.summary
|
468
|
-
boottime = vm_object.runtime.bootTime if vm_object.runtime
|
469
|
-
powerstate = vm_object.runtime.powerState if vm_object.runtime
|
471
|
+
hostname = vm_object.summary.guest.hostName if vm_object.summary&.guest && vm_object.summary.guest.hostName
|
472
|
+
boottime = vm_object.runtime.bootTime if vm_object.runtime&.bootTime
|
473
|
+
powerstate = vm_object.runtime.powerState if vm_object.runtime&.powerState
|
470
474
|
|
471
475
|
hash = {
|
472
476
|
'name' => vm_object.name,
|
@@ -474,16 +478,16 @@ module Vmpooler
|
|
474
478
|
'template' => pool_configuration['template'],
|
475
479
|
'poolname' => pool_name,
|
476
480
|
'boottime' => boottime,
|
477
|
-
'powerstate' => powerstate
|
481
|
+
'powerstate' => powerstate
|
478
482
|
}
|
479
483
|
|
480
484
|
hash
|
481
485
|
end
|
482
486
|
|
483
487
|
# vSphere helper methods
|
484
|
-
ADAPTER_TYPE = 'lsiLogic'
|
485
|
-
DISK_TYPE = 'thin'
|
486
|
-
DISK_MODE = 'persistent'
|
488
|
+
ADAPTER_TYPE = 'lsiLogic'
|
489
|
+
DISK_TYPE = 'thin'
|
490
|
+
DISK_MODE = 'persistent'
|
487
491
|
|
488
492
|
def ensured_vsphere_connection(connection_pool_object)
|
489
493
|
connection_pool_object[:connection] = connect_to_vsphere unless vsphere_connection_ok?(connection_pool_object[:connection])
|
@@ -492,9 +496,9 @@ module Vmpooler
|
|
492
496
|
|
493
497
|
def vsphere_connection_ok?(connection)
|
494
498
|
_result = connection.serviceInstance.CurrentTime
|
495
|
-
|
496
|
-
rescue
|
497
|
-
|
499
|
+
true
|
500
|
+
rescue StandardError
|
501
|
+
false
|
498
502
|
end
|
499
503
|
|
500
504
|
def connect_to_vsphere
|
@@ -507,10 +511,11 @@ module Vmpooler
|
|
507
511
|
password: provider_config['password'],
|
508
512
|
insecure: provider_config['insecure'] || false
|
509
513
|
metrics.increment('connect.open')
|
510
|
-
|
511
|
-
rescue =>
|
514
|
+
connection
|
515
|
+
rescue StandardError => e
|
512
516
|
metrics.increment('connect.fail')
|
513
|
-
raise
|
517
|
+
raise e if try >= max_tries
|
518
|
+
|
514
519
|
sleep(try * retry_factor)
|
515
520
|
try += 1
|
516
521
|
retry
|
@@ -610,6 +615,7 @@ module Vmpooler
|
|
610
615
|
def find_datastore(datastorename, connection, datacentername)
|
611
616
|
datacenter = connection.serviceInstance.find_datacenter(datacentername)
|
612
617
|
raise("Datacenter #{datacentername} does not exist") if datacenter.nil?
|
618
|
+
|
613
619
|
datacenter.find_datastore(datastorename)
|
614
620
|
end
|
615
621
|
|
@@ -625,9 +631,7 @@ module Vmpooler
|
|
625
631
|
devices = find_disk_devices(vm)
|
626
632
|
|
627
633
|
devices.keys.sort.each do |device|
|
628
|
-
if devices[device]['children'].length < 15
|
629
|
-
return find_device(vm, devices[device]['device'].deviceInfo.label)
|
630
|
-
end
|
634
|
+
return find_device(vm, devices[device]['device'].deviceInfo.label) if devices[device]['children'].length < 15
|
631
635
|
end
|
632
636
|
|
633
637
|
nil
|
@@ -667,6 +671,7 @@ module Vmpooler
|
|
667
671
|
|
668
672
|
devices.keys.sort.each do |c|
|
669
673
|
next unless controller.key == devices[c]['device'].key
|
674
|
+
|
670
675
|
used_unit_numbers.push(devices[c]['device'].scsiCtlrUnitNumber)
|
671
676
|
devices[c]['children'].each do |disk|
|
672
677
|
used_unit_numbers.push(disk.unitNumber)
|
@@ -674,12 +679,10 @@ module Vmpooler
|
|
674
679
|
end
|
675
680
|
|
676
681
|
(0..15).each do |scsi_id|
|
677
|
-
if used_unit_numbers.grep(scsi_id).length <= 0
|
678
|
-
available_unit_numbers.push(scsi_id)
|
679
|
-
end
|
682
|
+
available_unit_numbers.push(scsi_id) if used_unit_numbers.grep(scsi_id).length <= 0
|
680
683
|
end
|
681
684
|
|
682
|
-
available_unit_numbers.
|
685
|
+
available_unit_numbers.min
|
683
686
|
end
|
684
687
|
|
685
688
|
# Finds a folder object by inventory path
|
@@ -692,17 +695,19 @@ module Vmpooler
|
|
692
695
|
# Returns nil when the object found is not a folder
|
693
696
|
pool_configuration = pool_config(pool_name)
|
694
697
|
return nil if pool_configuration.nil?
|
698
|
+
|
695
699
|
folder = pool_configuration['folder']
|
696
700
|
datacenter = get_target_datacenter_from_config(pool_name)
|
697
701
|
return nil if datacenter.nil?
|
698
702
|
|
699
|
-
propSpecs = {
|
700
|
-
:
|
701
|
-
:
|
703
|
+
propSpecs = { # rubocop:disable Naming/VariableName
|
704
|
+
entity: self,
|
705
|
+
inventoryPath: "#{datacenter}/vm/#{folder}"
|
702
706
|
}
|
703
707
|
|
704
|
-
folder_object = connection.searchIndex.FindByInventoryPath(propSpecs)
|
708
|
+
folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
|
705
709
|
return nil unless folder_object.class == RbVmomi::VIM::Folder
|
710
|
+
|
706
711
|
folder_object
|
707
712
|
end
|
708
713
|
|
@@ -752,15 +757,17 @@ module Vmpooler
|
|
752
757
|
def cpu_utilization_for(host)
|
753
758
|
cpu_usage = host.summary.quickStats.overallCpuUsage
|
754
759
|
return nil if cpu_usage.nil?
|
760
|
+
|
755
761
|
cpu_size = host.summary.hardware.cpuMhz * host.summary.hardware.numCpuCores
|
756
|
-
|
762
|
+
cpu_usage.fdiv(cpu_size) * 100
|
757
763
|
end
|
758
764
|
|
759
765
|
def memory_utilization_for(host)
|
760
766
|
memory_usage = host.summary.quickStats.overallMemoryUsage
|
761
767
|
return nil if memory_usage.nil?
|
768
|
+
|
762
769
|
memory_size = host.summary.hardware.memorySize / 1024 / 1024
|
763
|
-
|
770
|
+
memory_usage.fdiv(memory_size) * 100
|
764
771
|
end
|
765
772
|
|
766
773
|
def get_average_cluster_utilization(hosts)
|
@@ -769,13 +776,13 @@ module Vmpooler
|
|
769
776
|
end
|
770
777
|
|
771
778
|
def build_compatible_hosts_lists(hosts, percentage)
|
772
|
-
hosts_with_arch_versions = hosts.map
|
779
|
+
hosts_with_arch_versions = hosts.map do |h|
|
773
780
|
{
|
774
|
-
|
775
|
-
|
776
|
-
|
781
|
+
'utilization' => h[0],
|
782
|
+
'host_object' => h[1],
|
783
|
+
'architecture' => get_host_cpu_arch_version(h[1])
|
777
784
|
}
|
778
|
-
|
785
|
+
end
|
779
786
|
versions = hosts_with_arch_versions.map { |host| host['architecture'] }.uniq
|
780
787
|
architectures = {}
|
781
788
|
versions.each do |version|
|
@@ -787,7 +794,6 @@ module Vmpooler
|
|
787
794
|
end
|
788
795
|
|
789
796
|
versions.each do |version|
|
790
|
-
targets = []
|
791
797
|
targets = select_least_used_hosts(architectures[version], percentage)
|
792
798
|
architectures[version] = targets
|
793
799
|
end
|
@@ -796,12 +802,13 @@ module Vmpooler
|
|
796
802
|
|
797
803
|
def select_least_used_hosts(hosts, percentage)
|
798
804
|
raise('Provided hosts list to select_least_used_hosts is empty') if hosts.empty?
|
805
|
+
|
799
806
|
average_utilization = get_average_cluster_utilization(hosts)
|
800
807
|
least_used_hosts = []
|
801
808
|
hosts.each do |host|
|
802
809
|
least_used_hosts << host if host[0] <= average_utilization
|
803
810
|
end
|
804
|
-
hosts_to_select = (hosts.count * (percentage
|
811
|
+
hosts_to_select = (hosts.count * (percentage / 100.0)).to_int
|
805
812
|
hosts_to_select = hosts.count - 1 if percentage == 100
|
806
813
|
least_used_hosts.sort[0..hosts_to_select].map { |host| host[1].name }
|
807
814
|
end
|
@@ -811,8 +818,10 @@ module Vmpooler
|
|
811
818
|
connection = ensured_vsphere_connection(pool_object)
|
812
819
|
cluster_object = find_cluster(cluster, connection, datacentername)
|
813
820
|
raise("Cluster #{cluster} cannot be found") if cluster_object.nil?
|
821
|
+
|
814
822
|
target_hosts = get_cluster_host_utilization(cluster_object)
|
815
823
|
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?
|
824
|
+
|
816
825
|
architectures = build_compatible_hosts_lists(target_hosts, percentage)
|
817
826
|
least_used_hosts = select_least_used_hosts(target_hosts, percentage)
|
818
827
|
{
|
@@ -825,6 +834,7 @@ module Vmpooler
|
|
825
834
|
def find_host_by_dnsname(connection, dnsname)
|
826
835
|
host_object = connection.searchIndex.FindByDnsName(dnsName: dnsname, vmSearch: false)
|
827
836
|
return nil if host_object.nil?
|
837
|
+
|
828
838
|
host_object
|
829
839
|
end
|
830
840
|
|
@@ -832,7 +842,8 @@ module Vmpooler
|
|
832
842
|
cluster_object = find_cluster(cluster, connection, datacentername)
|
833
843
|
target_hosts = get_cluster_host_utilization(cluster_object)
|
834
844
|
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?
|
835
|
-
|
845
|
+
|
846
|
+
least_used_host = target_hosts.min[1]
|
836
847
|
least_used_host
|
837
848
|
end
|
838
849
|
|
@@ -848,7 +859,7 @@ module Vmpooler
|
|
848
859
|
cv = connection.serviceContent.viewManager.CreateContainerView(
|
849
860
|
container: datacenter.hostFolder,
|
850
861
|
type: ['ComputeResource', 'ClusterComputeResource'],
|
851
|
-
recursive: true
|
862
|
+
recursive: true
|
852
863
|
)
|
853
864
|
cluster = cv.view.find { |cluster_object| cluster_object.name == cluster }
|
854
865
|
cv.DestroyView
|
@@ -870,7 +881,8 @@ module Vmpooler
|
|
870
881
|
cluster = source_host.parent
|
871
882
|
target_hosts = get_cluster_host_utilization(cluster, model)
|
872
883
|
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?
|
873
|
-
|
884
|
+
|
885
|
+
target_host = target_hosts.min[1]
|
874
886
|
[target_host, target_host.name]
|
875
887
|
end
|
876
888
|
|
@@ -878,12 +890,12 @@ module Vmpooler
|
|
878
890
|
get_snapshot_list(vm.snapshot.rootSnapshotList, snapshotname) if vm.snapshot
|
879
891
|
end
|
880
892
|
|
881
|
-
def build_propSpecs(datacenter, folder, vmname)
|
882
|
-
propSpecs = {
|
893
|
+
def build_propSpecs(datacenter, folder, vmname) # rubocop:disable Naming/MethodName
|
894
|
+
propSpecs = { # rubocop:disable Naming/VariableName
|
883
895
|
entity => self,
|
884
896
|
:inventoryPath => "#{datacenter}/vm/#{folder}/#{vmname}"
|
885
897
|
}
|
886
|
-
propSpecs
|
898
|
+
propSpecs # rubocop:disable Naming/VariableName
|
887
899
|
end
|
888
900
|
|
889
901
|
def find_vm(pool_name, vmname, connection)
|
@@ -891,16 +903,17 @@ module Vmpooler
|
|
891
903
|
# Returns nil when a VM, or pool configuration, cannot be found
|
892
904
|
pool_configuration = pool_config(pool_name)
|
893
905
|
return nil if pool_configuration.nil?
|
906
|
+
|
894
907
|
folder = pool_configuration['folder']
|
895
908
|
datacenter = get_target_datacenter_from_config(pool_name)
|
896
909
|
return nil if datacenter.nil?
|
897
910
|
|
898
|
-
propSpecs = {
|
899
|
-
:
|
900
|
-
:
|
911
|
+
propSpecs = { # rubocop:disable Naming/VariableName
|
912
|
+
entity: self,
|
913
|
+
inventoryPath: "#{datacenter}/vm/#{folder}/#{vmname}"
|
901
914
|
}
|
902
915
|
|
903
|
-
connection.searchIndex.FindByInventoryPath(propSpecs)
|
916
|
+
connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
|
904
917
|
end
|
905
918
|
|
906
919
|
def get_base_vm_container_from(connection)
|
@@ -929,8 +942,10 @@ module Vmpooler
|
|
929
942
|
def get_vm_details(pool_name, vm_name, connection)
|
930
943
|
vm_object = find_vm(pool_name, vm_name, connection)
|
931
944
|
return nil if vm_object.nil?
|
932
|
-
|
945
|
+
|
946
|
+
parent_host_object = vm_object.summary.runtime.host if vm_object.summary&.runtime && vm_object.summary.runtime.host
|
933
947
|
raise('Unable to determine which host the VM is running on') if parent_host_object.nil?
|
948
|
+
|
934
949
|
parent_host = parent_host_object.name
|
935
950
|
architecture = get_host_cpu_arch_version(parent_host_object)
|
936
951
|
{
|
@@ -944,6 +959,7 @@ module Vmpooler
|
|
944
959
|
migration_limit = config[:config]['migration_limit']
|
945
960
|
return false unless migration_limit.is_a? Integer
|
946
961
|
return true if migration_limit > 0
|
962
|
+
|
947
963
|
false
|
948
964
|
end
|
949
965
|
|
@@ -958,7 +974,7 @@ module Vmpooler
|
|
958
974
|
if migration_enabled? @config
|
959
975
|
if migration_count >= migration_limit
|
960
976
|
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")
|
961
|
-
|
977
|
+
break
|
962
978
|
end
|
963
979
|
run_select_hosts(pool_name, @provider_hosts)
|
964
980
|
if vm_in_target?(pool_name, vm_hash['host_name'], vm_hash['architecture'], @provider_hosts)
|
@@ -969,9 +985,9 @@ module Vmpooler
|
|
969
985
|
else
|
970
986
|
logger.log('s', "[ ] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
|
971
987
|
end
|
972
|
-
rescue
|
988
|
+
rescue StandardError
|
973
989
|
logger.log('s', "[!] [#{pool_name}] '#{vm_name}' is running on #{vm_hash['host_name']}")
|
974
|
-
raise
|
990
|
+
raise
|
975
991
|
end
|
976
992
|
end
|
977
993
|
end
|
@@ -991,11 +1007,11 @@ module Vmpooler
|
|
991
1007
|
def migrate_vm_and_record_timing(pool_name, vm_name, vm_hash, target_host_object, dest_host_name)
|
992
1008
|
start = Time.now
|
993
1009
|
migrate_vm_host(vm_hash['object'], target_host_object)
|
994
|
-
finish = format('
|
1010
|
+
finish = format('%<time>.2f', time: Time.now - start)
|
995
1011
|
metrics.timing("migrate.#{pool_name}", finish)
|
996
1012
|
metrics.increment("migrate_from.#{vm_hash['host_name']}")
|
997
1013
|
metrics.increment("migrate_to.#{dest_host_name}")
|
998
|
-
checkout_to_migration = format('
|
1014
|
+
checkout_to_migration = format('%<time>.2f', time: Time.now - Time.parse($redis.hget("vmpooler__vm__#{vm_name}", 'checkout')))
|
999
1015
|
$redis.hset("vmpooler__vm__#{vm_name}", 'migration_time', finish)
|
1000
1016
|
$redis.hset("vmpooler__vm__#{vm_name}", 'checkout_to_migration', checkout_to_migration)
|
1001
1017
|
finish
|
@@ -1008,8 +1024,9 @@ module Vmpooler
|
|
1008
1024
|
|
1009
1025
|
def create_folder(connection, new_folder, datacenter)
|
1010
1026
|
dc = connection.serviceInstance.find_datacenter(datacenter)
|
1011
|
-
folder_object = dc.vmFolder.traverse(new_folder,
|
1027
|
+
folder_object = dc.vmFolder.traverse(new_folder, RbVmomi::VIM::Folder, true)
|
1012
1028
|
raise("Cannot create folder #{new_folder}") if folder_object.nil?
|
1029
|
+
|
1013
1030
|
folder_object
|
1014
1031
|
end
|
1015
1032
|
|
@@ -1017,12 +1034,12 @@ module Vmpooler
|
|
1017
1034
|
datacenter = get_target_datacenter_from_config(pool['name'])
|
1018
1035
|
raise('cannot find datacenter') if datacenter.nil?
|
1019
1036
|
|
1020
|
-
propSpecs = {
|
1021
|
-
:
|
1022
|
-
:
|
1037
|
+
propSpecs = { # rubocop:disable Naming/VariableName
|
1038
|
+
entity: self,
|
1039
|
+
inventoryPath: "#{datacenter}/vm/#{pool['template']}"
|
1023
1040
|
}
|
1024
1041
|
|
1025
|
-
template_vm_object = connection.searchIndex.FindByInventoryPath(propSpecs)
|
1042
|
+
template_vm_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
|
1026
1043
|
raise("Pool #{pool['name']} specifies a template VM of #{pool['template']} which does not exist for the provider #{name}") if template_vm_object.nil?
|
1027
1044
|
|
1028
1045
|
template_vm_object
|
@@ -1041,12 +1058,14 @@ module Vmpooler
|
|
1041
1058
|
return false unless template.include?('/')
|
1042
1059
|
return false if template[0] == '/'
|
1043
1060
|
return false if template[-1] == '/'
|
1044
|
-
|
1061
|
+
|
1062
|
+
true
|
1045
1063
|
end
|
1046
1064
|
|
1047
1065
|
def get_disk_backing(pool)
|
1048
1066
|
return :moveChildMostDiskBacking if linked_clone?(pool)
|
1049
|
-
|
1067
|
+
|
1068
|
+
:moveAllDiskBackingsAndConsolidate
|
1050
1069
|
end
|
1051
1070
|
|
1052
1071
|
def linked_clone?(pool)
|