vmpooler 0.10.3 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|