vmpooler 0.11.1 → 0.13.1
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 +3 -1
- data/lib/vmpooler.rb +23 -1
- data/lib/vmpooler/api/helpers.rb +16 -16
- data/lib/vmpooler/api/v1.rb +294 -26
- data/lib/vmpooler/generic_connection_pool.rb +5 -23
- data/lib/vmpooler/pool_manager.rb +747 -384
- data/lib/vmpooler/providers/base.rb +2 -1
- data/lib/vmpooler/providers/dummy.rb +2 -2
- data/lib/vmpooler/providers/vsphere.rb +55 -35
- data/lib/vmpooler/version.rb +1 -1
- metadata +18 -4
@@ -15,35 +15,17 @@ module Vmpooler
|
|
15
15
|
@metric_prefix = 'connectionpool' if @metric_prefix.nil? || @metric_prefix == ''
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
def with_metrics(options = {})
|
21
|
-
Thread.handle_interrupt(Exception => :never) do
|
22
|
-
start = Time.now
|
23
|
-
conn = checkout(options)
|
24
|
-
timespan_ms = ((Time.now - start) * 1000).to_i
|
25
|
-
@metrics&.gauge(@metric_prefix + '.available', @available.length)
|
26
|
-
@metrics&.timing(@metric_prefix + '.waited', timespan_ms)
|
27
|
-
begin
|
28
|
-
Thread.handle_interrupt(Exception => :immediate) do
|
29
|
-
yield conn
|
30
|
-
end
|
31
|
-
ensure
|
32
|
-
checkin
|
33
|
-
@metrics&.gauge(@metric_prefix + '.available', @available.length)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
else
|
38
|
-
# jruby 1.7.x
|
39
|
-
def with_metrics(options = {})
|
18
|
+
def with_metrics(options = {})
|
19
|
+
Thread.handle_interrupt(Exception => :never) do
|
40
20
|
start = Time.now
|
41
21
|
conn = checkout(options)
|
42
22
|
timespan_ms = ((Time.now - start) * 1000).to_i
|
43
23
|
@metrics&.gauge(@metric_prefix + '.available', @available.length)
|
44
24
|
@metrics&.timing(@metric_prefix + '.waited', timespan_ms)
|
45
25
|
begin
|
46
|
-
|
26
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
27
|
+
yield conn
|
28
|
+
end
|
47
29
|
ensure
|
48
30
|
checkin
|
49
31
|
@metrics&.gauge(@metric_prefix + '.available', @available.length)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'vmpooler/providers'
|
4
4
|
require 'spicy-proton'
|
5
|
+
require 'resolv' # ruby standard lib
|
5
6
|
|
6
7
|
module Vmpooler
|
7
8
|
class PoolManager
|
@@ -9,7 +10,7 @@ module Vmpooler
|
|
9
10
|
CHECK_LOOP_DELAY_MAX_DEFAULT = 60
|
10
11
|
CHECK_LOOP_DELAY_DECAY_DEFAULT = 2.0
|
11
12
|
|
12
|
-
def initialize(config, logger,
|
13
|
+
def initialize(config, logger, redis_connection_pool, metrics)
|
13
14
|
$config = config
|
14
15
|
|
15
16
|
# Load logger library
|
@@ -18,19 +19,19 @@ module Vmpooler
|
|
18
19
|
# metrics logging handle
|
19
20
|
$metrics = metrics
|
20
21
|
|
21
|
-
#
|
22
|
-
|
22
|
+
# Redis connection pool
|
23
|
+
@redis = redis_connection_pool
|
23
24
|
|
24
25
|
# VM Provider objects
|
25
|
-
$providers =
|
26
|
+
$providers = Concurrent::Hash.new
|
26
27
|
|
27
28
|
# Our thread-tracker object
|
28
|
-
$threads =
|
29
|
+
$threads = Concurrent::Hash.new
|
29
30
|
|
30
31
|
# Pool mutex
|
31
|
-
@reconfigure_pool =
|
32
|
+
@reconfigure_pool = Concurrent::Hash.new
|
32
33
|
|
33
|
-
@vm_mutex =
|
34
|
+
@vm_mutex = Concurrent::Hash.new
|
34
35
|
|
35
36
|
# Name generator for generating host names
|
36
37
|
@name_generator = Spicy::Proton.new
|
@@ -45,24 +46,26 @@ module Vmpooler
|
|
45
46
|
|
46
47
|
# Place pool configuration in redis so an API instance can discover running pool configuration
|
47
48
|
def load_pools_to_redis
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
49
|
+
@redis.with_metrics do |redis|
|
50
|
+
previously_configured_pools = redis.smembers('vmpooler__pools')
|
51
|
+
currently_configured_pools = []
|
52
|
+
config[:pools].each do |pool|
|
53
|
+
currently_configured_pools << pool['name']
|
54
|
+
redis.sadd('vmpooler__pools', pool['name'])
|
55
|
+
pool_keys = pool.keys
|
56
|
+
pool_keys.delete('alias')
|
57
|
+
to_set = {}
|
58
|
+
pool_keys.each do |k|
|
59
|
+
to_set[k] = pool[k]
|
60
|
+
end
|
61
|
+
to_set['alias'] = pool['alias'].join(',') if to_set.key?('alias')
|
62
|
+
redis.hmset("vmpooler__pool__#{pool['name']}", to_set.to_a.flatten) unless to_set.empty?
|
58
63
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
$redis.srem('vmpooler__pools', pool)
|
65
|
-
$redis.del("vmpooler__pool__#{pool}")
|
64
|
+
previously_configured_pools.each do |pool|
|
65
|
+
unless currently_configured_pools.include? pool
|
66
|
+
redis.srem('vmpooler__pools', pool)
|
67
|
+
redis.del("vmpooler__pool__#{pool}")
|
68
|
+
end
|
66
69
|
end
|
67
70
|
end
|
68
71
|
nil
|
@@ -75,7 +78,9 @@ module Vmpooler
|
|
75
78
|
_check_pending_vm(vm, pool, timeout, provider)
|
76
79
|
rescue StandardError => e
|
77
80
|
$logger.log('s', "[!] [#{pool}] '#{vm}' #{timeout} #{provider} errored while checking a pending vm : #{e}")
|
78
|
-
|
81
|
+
@redis.with_metrics do |redis|
|
82
|
+
fail_pending_vm(vm, pool, timeout, redis)
|
83
|
+
end
|
79
84
|
raise
|
80
85
|
end
|
81
86
|
end
|
@@ -86,31 +91,38 @@ module Vmpooler
|
|
86
91
|
return if mutex.locked?
|
87
92
|
|
88
93
|
mutex.synchronize do
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
94
|
+
@redis.with_metrics do |redis|
|
95
|
+
request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id')
|
96
|
+
if provider.vm_ready?(pool, vm)
|
97
|
+
move_pending_vm_to_ready(vm, pool, redis, request_id)
|
98
|
+
else
|
99
|
+
fail_pending_vm(vm, pool, timeout, redis)
|
100
|
+
end
|
93
101
|
end
|
94
102
|
end
|
95
103
|
end
|
96
104
|
|
97
|
-
def remove_nonexistent_vm(vm, pool)
|
98
|
-
|
105
|
+
def remove_nonexistent_vm(vm, pool, redis)
|
106
|
+
redis.srem("vmpooler__pending__#{pool}", vm)
|
99
107
|
$logger.log('d', "[!] [#{pool}] '#{vm}' no longer exists. Removing from pending.")
|
100
108
|
end
|
101
109
|
|
102
|
-
def fail_pending_vm(vm, pool, timeout, exists = true)
|
103
|
-
clone_stamp =
|
104
|
-
return true unless clone_stamp
|
110
|
+
def fail_pending_vm(vm, pool, timeout, redis, exists = true)
|
111
|
+
clone_stamp = redis.hget("vmpooler__vm__#{vm}", 'clone')
|
105
112
|
|
106
113
|
time_since_clone = (Time.now - Time.parse(clone_stamp)) / 60
|
107
114
|
if time_since_clone > timeout
|
108
115
|
if exists
|
109
|
-
|
116
|
+
request_id = redis.hget("vmpooler__vm__#{vm}", 'request_id')
|
117
|
+
pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias') if request_id
|
118
|
+
redis.multi
|
119
|
+
redis.smove('vmpooler__pending__' + pool, 'vmpooler__completed__' + pool, vm)
|
120
|
+
redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool}:1:#{request_id}") if request_id
|
121
|
+
redis.exec
|
110
122
|
$metrics.increment("errors.markedasfailed.#{pool}")
|
111
123
|
$logger.log('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes")
|
112
124
|
else
|
113
|
-
remove_nonexistent_vm(vm, pool)
|
125
|
+
remove_nonexistent_vm(vm, pool, redis)
|
114
126
|
end
|
115
127
|
end
|
116
128
|
true
|
@@ -119,28 +131,54 @@ module Vmpooler
|
|
119
131
|
false
|
120
132
|
end
|
121
133
|
|
122
|
-
def move_pending_vm_to_ready(vm, pool)
|
123
|
-
clone_time =
|
124
|
-
finish = format('%<time>.2f', time: Time.now - Time.parse(clone_time))
|
134
|
+
def move_pending_vm_to_ready(vm, pool, redis, request_id = nil)
|
135
|
+
clone_time = redis.hget('vmpooler__vm__' + vm, 'clone')
|
136
|
+
finish = format('%<time>.2f', time: Time.now - Time.parse(clone_time))
|
125
137
|
|
126
|
-
|
127
|
-
|
128
|
-
|
138
|
+
if request_id
|
139
|
+
ondemandrequest_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
|
140
|
+
if ondemandrequest_hash['status'] == 'failed'
|
141
|
+
move_vm_queue(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time")
|
142
|
+
return nil
|
143
|
+
elsif ondemandrequest_hash['status'] == 'deleted'
|
144
|
+
move_vm_queue(pool, vm, 'pending', 'completed', redis, "moved to completed queue. '#{request_id}' has been deleted")
|
145
|
+
return nil
|
146
|
+
end
|
147
|
+
pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias')
|
148
|
+
|
149
|
+
redis.pipelined do
|
150
|
+
redis.hset("vmpooler__active__#{pool}", vm, Time.now)
|
151
|
+
redis.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
|
152
|
+
redis.hset("vmpooler__vm__#{vm}", 'token:token', ondemandrequest_hash['token:token']) if ondemandrequest_hash['token:token']
|
153
|
+
redis.hset("vmpooler__vm__#{vm}", 'token:user', ondemandrequest_hash['token:user']) if ondemandrequest_hash['token:user']
|
154
|
+
redis.sadd("vmpooler__#{request_id}__#{pool_alias}__#{pool}", vm)
|
155
|
+
end
|
156
|
+
move_vm_queue(pool, vm, 'pending', 'running', redis)
|
157
|
+
check_ondemand_request_ready(request_id, redis)
|
158
|
+
else
|
159
|
+
redis.smove('vmpooler__pending__' + pool, 'vmpooler__ready__' + pool, vm)
|
160
|
+
end
|
129
161
|
|
130
|
-
|
131
|
-
|
162
|
+
redis.pipelined do
|
163
|
+
redis.hset('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm, finish) # maybe remove as this is never used by vmpooler itself?
|
164
|
+
redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
|
165
|
+
|
166
|
+
# last boot time is displayed in API, and used by alarming script
|
167
|
+
redis.hset('vmpooler__lastboot', pool, Time.now)
|
168
|
+
end
|
132
169
|
|
133
170
|
$metrics.timing("time_to_ready_state.#{pool}", finish)
|
134
|
-
$logger.log('s', "[>] [#{pool}] '#{vm}' moved from 'pending' to 'ready' queue")
|
171
|
+
$logger.log('s', "[>] [#{pool}] '#{vm}' moved from 'pending' to 'ready' queue") unless request_id
|
172
|
+
$logger.log('s', "[>] [#{pool}] '#{vm}' is 'ready' for request '#{request_id}'") if request_id
|
135
173
|
end
|
136
174
|
|
137
|
-
def vm_still_ready?(pool_name, vm_name, provider)
|
175
|
+
def vm_still_ready?(pool_name, vm_name, provider, redis)
|
138
176
|
# Check if the VM is still ready/available
|
139
177
|
return true if provider.vm_ready?(pool_name, vm_name)
|
140
178
|
|
141
179
|
raise("VM #{vm_name} is not ready")
|
142
180
|
rescue StandardError
|
143
|
-
move_vm_queue(pool_name, vm_name, 'ready', 'completed', "is unreachable, removed from 'ready' queue")
|
181
|
+
move_vm_queue(pool_name, vm_name, 'ready', 'completed', redis, "is unreachable, removed from 'ready' queue")
|
144
182
|
end
|
145
183
|
|
146
184
|
def check_ready_vm(vm, pool_name, ttl, provider)
|
@@ -160,34 +198,35 @@ module Vmpooler
|
|
160
198
|
return if mutex.locked?
|
161
199
|
|
162
200
|
mutex.synchronize do
|
163
|
-
|
164
|
-
|
201
|
+
@redis.with_metrics do |redis|
|
202
|
+
check_stamp = redis.hget('vmpooler__vm__' + vm, 'check')
|
203
|
+
last_checked_too_soon = ((Time.now - Time.parse(check_stamp)).to_i < $config[:config]['vm_checktime'] * 60) if check_stamp
|
204
|
+
break if check_stamp && last_checked_too_soon
|
165
205
|
|
166
|
-
|
167
|
-
|
168
|
-
if ttl > 0
|
206
|
+
redis.hset('vmpooler__vm__' + vm, 'check', Time.now)
|
207
|
+
# Check if the hosts TTL has expired
|
169
208
|
# if 'boottime' is nil, set bootime to beginning of unix epoch, forces TTL to be assumed expired
|
170
|
-
boottime =
|
209
|
+
boottime = redis.hget("vmpooler__vm__#{vm}", 'ready')
|
171
210
|
if boottime
|
172
211
|
boottime = Time.parse(boottime)
|
173
212
|
else
|
174
213
|
boottime = Time.at(0)
|
175
214
|
end
|
176
|
-
if (
|
177
|
-
|
215
|
+
if (Time.now - boottime).to_i > ttl * 60
|
216
|
+
redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
|
178
217
|
|
179
218
|
$logger.log('d', "[!] [#{pool_name}] '#{vm}' reached end of TTL after #{ttl} minutes, removed from 'ready' queue")
|
180
|
-
return
|
219
|
+
return nil
|
181
220
|
end
|
182
|
-
end
|
183
221
|
|
184
|
-
|
222
|
+
break if mismatched_hostname?(vm, pool_name, provider, redis)
|
185
223
|
|
186
|
-
|
224
|
+
vm_still_ready?(pool_name, vm, provider, redis)
|
225
|
+
end
|
187
226
|
end
|
188
227
|
end
|
189
228
|
|
190
|
-
def mismatched_hostname?(vm, pool_name, provider)
|
229
|
+
def mismatched_hostname?(vm, pool_name, provider, redis)
|
191
230
|
pool_config = $config[:pools][$config[:pool_index][pool_name]]
|
192
231
|
check_hostname = pool_config['check_hostname_for_mismatch']
|
193
232
|
check_hostname = $config[:config]['check_ready_vm_hostname_for_mismatch'] if check_hostname.nil?
|
@@ -196,7 +235,7 @@ module Vmpooler
|
|
196
235
|
# Wait one minute before checking a VM for hostname mismatch
|
197
236
|
# When checking as soon as the VM passes the ready test the instance
|
198
237
|
# often doesn't report its hostname yet causing the VM to be removed immediately
|
199
|
-
vm_ready_time =
|
238
|
+
vm_ready_time = redis.hget("vmpooler__vm__#{vm}", 'ready')
|
200
239
|
if vm_ready_time
|
201
240
|
wait_before_checking = 60
|
202
241
|
time_since_ready = (Time.now - Time.parse(vm_ready_time)).to_i
|
@@ -213,7 +252,7 @@ module Vmpooler
|
|
213
252
|
return if hostname.empty?
|
214
253
|
return if hostname == vm
|
215
254
|
|
216
|
-
|
255
|
+
redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
|
217
256
|
$logger.log('d', "[!] [#{pool_name}] '#{vm}' has mismatched hostname #{hostname}, removed from 'ready' queue")
|
218
257
|
true
|
219
258
|
end
|
@@ -234,49 +273,61 @@ module Vmpooler
|
|
234
273
|
return if mutex.locked?
|
235
274
|
|
236
275
|
mutex.synchronize do
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
276
|
+
catch :stop_checking do
|
277
|
+
@redis.with_metrics do |redis|
|
278
|
+
# Check that VM is within defined lifetime
|
279
|
+
checkouttime = redis.hget('vmpooler__active__' + pool, vm)
|
280
|
+
if checkouttime
|
281
|
+
time_since_checkout = Time.now - Time.parse(checkouttime)
|
282
|
+
running = time_since_checkout / 60 / 60
|
283
|
+
|
284
|
+
if (ttl.to_i > 0) && (running.to_i >= ttl.to_i)
|
285
|
+
move_vm_queue(pool, vm, 'running', 'completed', redis, "reached end of TTL after #{ttl} hours")
|
286
|
+
throw :stop_checking
|
287
|
+
end
|
288
|
+
end
|
247
289
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
290
|
+
if provider.vm_ready?(pool, vm)
|
291
|
+
throw :stop_checking
|
292
|
+
else
|
293
|
+
host = provider.get_vm(pool, vm)
|
252
294
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
295
|
+
if host
|
296
|
+
throw :stop_checking
|
297
|
+
else
|
298
|
+
move_vm_queue(pool, vm, 'running', 'completed', redis, 'is no longer in inventory, removing from running')
|
299
|
+
end
|
300
|
+
end
|
257
301
|
end
|
258
302
|
end
|
259
303
|
end
|
260
304
|
end
|
261
305
|
|
262
|
-
def move_vm_queue(pool, vm, queue_from, queue_to, msg = nil)
|
263
|
-
|
306
|
+
def move_vm_queue(pool, vm, queue_from, queue_to, redis, msg = nil)
|
307
|
+
redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
|
264
308
|
$logger.log('d', "[!] [#{pool}] '#{vm}' #{msg}") if msg
|
265
309
|
end
|
266
310
|
|
267
311
|
# Clone a VM
|
268
|
-
def clone_vm(pool_name, provider)
|
312
|
+
def clone_vm(pool_name, provider, request_id = nil, pool_alias = nil)
|
269
313
|
Thread.new do
|
270
314
|
begin
|
271
|
-
_clone_vm(pool_name, provider)
|
315
|
+
_clone_vm(pool_name, provider, request_id, pool_alias)
|
272
316
|
rescue StandardError => e
|
273
|
-
|
317
|
+
if request_id
|
318
|
+
$logger.log('s', "[!] [#{pool_name}] failed while cloning VM for request #{request_id} with an error: #{e}")
|
319
|
+
@redis.with_metrics do |redis|
|
320
|
+
redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool_name}:1:#{request_id}")
|
321
|
+
end
|
322
|
+
else
|
323
|
+
$logger.log('s', "[!] [#{pool_name}] failed while cloning VM with an error: #{e}")
|
324
|
+
end
|
274
325
|
raise
|
275
326
|
end
|
276
327
|
end
|
277
328
|
end
|
278
329
|
|
279
|
-
def generate_and_check_hostname
|
330
|
+
def generate_and_check_hostname
|
280
331
|
# Generate a randomized hostname. The total name must no longer than 15
|
281
332
|
# character including the hyphen. The shortest adjective in the corpus is
|
282
333
|
# three characters long. Therefore, we can technically select a noun up to 11
|
@@ -285,58 +336,104 @@ module Vmpooler
|
|
285
336
|
# letter adjectives, we actually limit the noun to 10 letters to avoid
|
286
337
|
# inviting more conflicts. We favor selecting a longer noun rather than a
|
287
338
|
# longer adjective because longer adjectives tend to be less fun.
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
339
|
+
@redis.with do |redis|
|
340
|
+
noun = @name_generator.noun(max: 10)
|
341
|
+
adjective = @name_generator.adjective(max: 14 - noun.length)
|
342
|
+
random_name = [adjective, noun].join('-')
|
343
|
+
hostname = $config[:config]['prefix'] + random_name
|
344
|
+
available = redis.hlen('vmpooler__vm__' + hostname) == 0
|
345
|
+
|
346
|
+
[hostname, available]
|
347
|
+
end
|
295
348
|
end
|
296
349
|
|
297
350
|
def find_unique_hostname(pool_name)
|
351
|
+
# generate hostname that is not already in use in vmpooler
|
352
|
+
# also check that no dns record already exists
|
298
353
|
hostname_retries = 0
|
299
354
|
max_hostname_retries = 3
|
300
355
|
while hostname_retries < max_hostname_retries
|
301
|
-
hostname,
|
302
|
-
|
356
|
+
hostname, hostname_available = generate_and_check_hostname
|
357
|
+
domain = $config[:config]['domain']
|
358
|
+
dns_ip, dns_available = check_dns_available(hostname, domain)
|
359
|
+
break if hostname_available && dns_available
|
303
360
|
|
304
361
|
hostname_retries += 1
|
305
|
-
|
306
|
-
|
362
|
+
|
363
|
+
if !hostname_available
|
364
|
+
$metrics.increment("errors.duplicatehostname.#{pool_name}")
|
365
|
+
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{hostname} was not unique (attempt \##{hostname_retries} of #{max_hostname_retries})")
|
366
|
+
elsif !dns_available
|
367
|
+
$metrics.increment("errors.staledns.#{hostname}")
|
368
|
+
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{hostname} already exists in DNS records (#{dns_ip}), stale DNS")
|
369
|
+
end
|
307
370
|
end
|
308
371
|
|
309
|
-
raise "Unable to generate a unique hostname after #{hostname_retries} attempts. The last hostname checked was #{hostname}" unless
|
372
|
+
raise "Unable to generate a unique hostname after #{hostname_retries} attempts. The last hostname checked was #{hostname}" unless hostname_available && dns_available
|
310
373
|
|
311
374
|
hostname
|
312
375
|
end
|
313
376
|
|
314
|
-
def
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
$redis.sadd('vmpooler__pending__' + pool_name, new_vmname)
|
319
|
-
$redis.hset('vmpooler__vm__' + new_vmname, 'clone', Time.now)
|
320
|
-
$redis.hset('vmpooler__vm__' + new_vmname, 'template', pool_name)
|
321
|
-
|
377
|
+
def check_dns_available(vm_name, domain = nil)
|
378
|
+
# Query the DNS for the name we want to create and if it already exists, mark it unavailable
|
379
|
+
# This protects against stale DNS records
|
380
|
+
vm_name = "#{vm_name}.#{domain}" if domain
|
322
381
|
begin
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
382
|
+
dns_ip = Resolv.getaddress(vm_name)
|
383
|
+
rescue Resolv::ResolvError
|
384
|
+
# this is the expected case, swallow the error
|
385
|
+
# eg "no address for blah-daisy"
|
386
|
+
return ['', true]
|
387
|
+
end
|
388
|
+
[dns_ip, false]
|
389
|
+
end
|
327
390
|
|
328
|
-
|
329
|
-
|
330
|
-
|
391
|
+
def _clone_vm(pool_name, provider, request_id = nil, pool_alias = nil)
|
392
|
+
new_vmname = find_unique_hostname(pool_name)
|
393
|
+
mutex = vm_mutex(new_vmname)
|
394
|
+
mutex.synchronize do
|
395
|
+
@redis.with_metrics do |redis|
|
396
|
+
# Add VM to Redis inventory ('pending' pool)
|
397
|
+
redis.multi
|
398
|
+
redis.sadd('vmpooler__pending__' + pool_name, new_vmname)
|
399
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'clone', Time.now)
|
400
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'template', pool_name) # This value is used to represent the pool.
|
401
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'pool', pool_name)
|
402
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'request_id', request_id) if request_id
|
403
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'pool_alias', pool_alias) if pool_alias
|
404
|
+
redis.exec
|
405
|
+
end
|
331
406
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
407
|
+
begin
|
408
|
+
$logger.log('d', "[ ] [#{pool_name}] Starting to clone '#{new_vmname}'")
|
409
|
+
start = Time.now
|
410
|
+
provider.create_vm(pool_name, new_vmname)
|
411
|
+
finish = format('%<time>.2f', time: Time.now - start)
|
412
|
+
|
413
|
+
@redis.with_metrics do |redis|
|
414
|
+
redis.pipelined do
|
415
|
+
redis.hset('vmpooler__clone__' + Date.today.to_s, pool_name + ':' + new_vmname, finish)
|
416
|
+
redis.hset('vmpooler__vm__' + new_vmname, 'clone_time', finish)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
$logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds")
|
420
|
+
|
421
|
+
$metrics.timing("clone.#{pool_name}", finish)
|
422
|
+
rescue StandardError
|
423
|
+
@redis.with_metrics do |redis|
|
424
|
+
redis.pipelined do
|
425
|
+
redis.srem("vmpooler__pending__#{pool_name}", new_vmname)
|
426
|
+
expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
|
427
|
+
redis.expire("vmpooler__vm__#{new_vmname}", expiration_ttl)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
raise
|
431
|
+
ensure
|
432
|
+
@redis.with_metrics do |redis|
|
433
|
+
redis.decr('vmpooler__tasks__ondemandclone') if request_id
|
434
|
+
redis.decr('vmpooler__tasks__clone') unless request_id
|
435
|
+
end
|
436
|
+
end
|
340
437
|
end
|
341
438
|
end
|
342
439
|
|
@@ -357,36 +454,42 @@ module Vmpooler
|
|
357
454
|
return if mutex.locked?
|
358
455
|
|
359
456
|
mutex.synchronize do
|
360
|
-
|
361
|
-
|
457
|
+
@redis.with_metrics do |redis|
|
458
|
+
redis.pipelined do
|
459
|
+
redis.hdel('vmpooler__active__' + pool, vm)
|
460
|
+
redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
|
362
461
|
|
363
|
-
|
364
|
-
|
462
|
+
# Auto-expire metadata key
|
463
|
+
redis.expire('vmpooler__vm__' + vm, ($config[:redis]['data_ttl'].to_i * 60 * 60))
|
464
|
+
end
|
365
465
|
|
366
|
-
|
466
|
+
start = Time.now
|
367
467
|
|
368
|
-
|
468
|
+
provider.destroy_vm(pool, vm)
|
369
469
|
|
370
|
-
|
470
|
+
redis.srem('vmpooler__completed__' + pool, vm)
|
371
471
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
472
|
+
finish = format('%<time>.2f', time: Time.now - start)
|
473
|
+
$logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
|
474
|
+
$metrics.timing("destroy.#{pool}", finish)
|
475
|
+
get_vm_usage_labels(vm, redis)
|
476
|
+
end
|
376
477
|
end
|
377
478
|
dereference_mutex(vm)
|
378
479
|
end
|
379
480
|
|
380
|
-
def get_vm_usage_labels(vm)
|
481
|
+
def get_vm_usage_labels(vm, redis)
|
381
482
|
return unless $config[:config]['usage_stats']
|
382
483
|
|
383
|
-
|
484
|
+
redis.multi
|
485
|
+
redis.hget("vmpooler__vm__#{vm}", 'checkout')
|
486
|
+
redis.hget("vmpooler__vm__#{vm}", 'tag:jenkins_build_url')
|
487
|
+
redis.hget("vmpooler__vm__#{vm}", 'token:user')
|
488
|
+
redis.hget("vmpooler__vm__#{vm}", 'template')
|
489
|
+
checkout, jenkins_build_url, user, poolname = redis.exec
|
384
490
|
return if checkout.nil?
|
385
491
|
|
386
|
-
|
387
|
-
user = $redis.hget("vmpooler__vm__#{vm}", 'token:user') || 'unauthenticated'
|
388
|
-
poolname = $redis.hget("vmpooler__vm__#{vm}", 'template')
|
389
|
-
|
492
|
+
user ||= 'unauthenticated'
|
390
493
|
unless jenkins_build_url
|
391
494
|
user = user.gsub('.', '_')
|
392
495
|
$metrics.increment("usage.#{user}.#{poolname}")
|
@@ -420,7 +523,8 @@ module Vmpooler
|
|
420
523
|
|
421
524
|
$metrics.increment(metric_parts.join('.'))
|
422
525
|
rescue StandardError => e
|
423
|
-
logger.log('d', "[!] [#{poolname}] failed while evaluating usage labels on '#{vm}' with an error: #{e}")
|
526
|
+
$logger.log('d', "[!] [#{poolname}] failed while evaluating usage labels on '#{vm}' with an error: #{e}")
|
527
|
+
raise
|
424
528
|
end
|
425
529
|
|
426
530
|
def component_to_test(match, labels_string)
|
@@ -506,10 +610,12 @@ module Vmpooler
|
|
506
610
|
finish = format('%<time>.2f', time: Time.now - start)
|
507
611
|
|
508
612
|
if result
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
613
|
+
@redis.with_metrics do |redis|
|
614
|
+
rdisks = redis.hget('vmpooler__vm__' + vm_name, 'disk')
|
615
|
+
disks = rdisks ? rdisks.split(':') : []
|
616
|
+
disks.push("+#{disk_size}gb")
|
617
|
+
redis.hset('vmpooler__vm__' + vm_name, 'disk', disks.join(':'))
|
618
|
+
end
|
513
619
|
|
514
620
|
$logger.log('s', "[+] [disk_manager] '#{vm_name}' attached #{disk_size}gb disk in #{finish} seconds")
|
515
621
|
else
|
@@ -539,7 +645,9 @@ module Vmpooler
|
|
539
645
|
finish = format('%<time>.2f', time: Time.now - start)
|
540
646
|
|
541
647
|
if result
|
542
|
-
|
648
|
+
@redis.with_metrics do |redis|
|
649
|
+
redis.hset('vmpooler__vm__' + vm_name, 'snapshot:' + snapshot_name, Time.now.to_s)
|
650
|
+
end
|
543
651
|
$logger.log('s', "[+] [snapshot_manager] '#{vm_name}' snapshot created in #{finish} seconds")
|
544
652
|
else
|
545
653
|
$logger.log('s', "[+] [snapshot_manager] Failed to snapshot '#{vm_name}'")
|
@@ -595,9 +703,9 @@ module Vmpooler
|
|
595
703
|
@default_providers ||= %w[vsphere dummy]
|
596
704
|
end
|
597
705
|
|
598
|
-
def get_pool_name_for_vm(vm_name)
|
706
|
+
def get_pool_name_for_vm(vm_name, redis)
|
599
707
|
# the 'template' is a bad name. Should really be 'poolname'
|
600
|
-
|
708
|
+
redis.hget('vmpooler__vm__' + vm_name, 'template')
|
601
709
|
end
|
602
710
|
|
603
711
|
# @param pool_name [String] - the name of the pool
|
@@ -629,19 +737,21 @@ module Vmpooler
|
|
629
737
|
end
|
630
738
|
|
631
739
|
def _check_disk_queue
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
740
|
+
@redis.with_metrics do |redis|
|
741
|
+
task_detail = redis.spop('vmpooler__tasks__disk')
|
742
|
+
unless task_detail.nil?
|
743
|
+
begin
|
744
|
+
vm_name, disk_size = task_detail.split(':')
|
745
|
+
pool_name = get_pool_name_for_vm(vm_name, redis)
|
746
|
+
raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
|
638
747
|
|
639
|
-
|
640
|
-
|
748
|
+
provider = get_provider_for_pool(pool_name)
|
749
|
+
raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
|
641
750
|
|
642
|
-
|
643
|
-
|
644
|
-
|
751
|
+
create_vm_disk(pool_name, vm_name, disk_size, provider)
|
752
|
+
rescue StandardError => e
|
753
|
+
$logger.log('s', "[!] [disk_manager] disk creation appears to have failed: #{e}")
|
754
|
+
end
|
645
755
|
end
|
646
756
|
end
|
647
757
|
end
|
@@ -665,37 +775,39 @@ module Vmpooler
|
|
665
775
|
end
|
666
776
|
|
667
777
|
def _check_snapshot_queue
|
668
|
-
|
778
|
+
@redis.with_metrics do |redis|
|
779
|
+
task_detail = redis.spop('vmpooler__tasks__snapshot')
|
669
780
|
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
781
|
+
unless task_detail.nil?
|
782
|
+
begin
|
783
|
+
vm_name, snapshot_name = task_detail.split(':')
|
784
|
+
pool_name = get_pool_name_for_vm(vm_name, redis)
|
785
|
+
raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
|
675
786
|
|
676
|
-
|
677
|
-
|
787
|
+
provider = get_provider_for_pool(pool_name)
|
788
|
+
raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
|
678
789
|
|
679
|
-
|
680
|
-
|
681
|
-
|
790
|
+
create_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
|
791
|
+
rescue StandardError => e
|
792
|
+
$logger.log('s', "[!] [snapshot_manager] snapshot create appears to have failed: #{e}")
|
793
|
+
end
|
682
794
|
end
|
683
|
-
end
|
684
795
|
|
685
|
-
|
796
|
+
task_detail = redis.spop('vmpooler__tasks__snapshot-revert')
|
686
797
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
798
|
+
unless task_detail.nil?
|
799
|
+
begin
|
800
|
+
vm_name, snapshot_name = task_detail.split(':')
|
801
|
+
pool_name = get_pool_name_for_vm(vm_name, redis)
|
802
|
+
raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
|
692
803
|
|
693
|
-
|
694
|
-
|
804
|
+
provider = get_provider_for_pool(pool_name)
|
805
|
+
raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
|
695
806
|
|
696
|
-
|
697
|
-
|
698
|
-
|
807
|
+
revert_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
|
808
|
+
rescue StandardError => e
|
809
|
+
$logger.log('s', "[!] [snapshot_manager] snapshot revert appears to have failed: #{e}")
|
810
|
+
end
|
699
811
|
end
|
700
812
|
end
|
701
813
|
end
|
@@ -705,7 +817,9 @@ module Vmpooler
|
|
705
817
|
begin
|
706
818
|
mutex = vm_mutex(vm_name)
|
707
819
|
mutex.synchronize do
|
708
|
-
|
820
|
+
@redis.with_metrics do |redis|
|
821
|
+
redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
|
822
|
+
end
|
709
823
|
provider.migrate_vm(pool_name, vm_name)
|
710
824
|
end
|
711
825
|
rescue StandardError => e
|
@@ -738,47 +852,65 @@ module Vmpooler
|
|
738
852
|
wakeup_by = Time.now + wakeup_period
|
739
853
|
return if time_passed?(:exit_by, exit_by)
|
740
854
|
|
741
|
-
|
855
|
+
@redis.with_metrics do |redis|
|
856
|
+
initial_ready_size = redis.scard("vmpooler__ready__#{options[:poolname]}") if options[:pool_size_change]
|
742
857
|
|
743
|
-
|
858
|
+
initial_clone_target = redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target]) if options[:clone_target_change]
|
744
859
|
|
745
|
-
|
860
|
+
initial_template = redis.hget('vmpooler__template__prepared', options[:poolname]) if options[:pool_template_change]
|
746
861
|
|
747
|
-
|
748
|
-
|
749
|
-
|
862
|
+
loop do
|
863
|
+
sleep(1)
|
864
|
+
break if time_passed?(:exit_by, exit_by)
|
750
865
|
|
751
|
-
|
752
|
-
|
753
|
-
|
866
|
+
# Check for wakeup events
|
867
|
+
if time_passed?(:wakeup_by, wakeup_by)
|
868
|
+
wakeup_by = Time.now + wakeup_period
|
754
869
|
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
870
|
+
# Wakeup if the number of ready VMs has changed
|
871
|
+
if options[:pool_size_change]
|
872
|
+
ready_size = redis.scard("vmpooler__ready__#{options[:poolname]}")
|
873
|
+
break unless ready_size == initial_ready_size
|
874
|
+
end
|
760
875
|
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
876
|
+
if options[:clone_target_change]
|
877
|
+
clone_target = redis.hget('vmpooler__config__clone_target}', options[:poolname])
|
878
|
+
if clone_target
|
879
|
+
break unless clone_target == initial_clone_target
|
880
|
+
end
|
765
881
|
end
|
766
|
-
end
|
767
882
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
883
|
+
if options[:pool_template_change]
|
884
|
+
configured_template = redis.hget('vmpooler__config__template', options[:poolname])
|
885
|
+
if configured_template
|
886
|
+
break unless initial_template == configured_template
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
if options[:pool_reset]
|
891
|
+
pending = redis.sismember('vmpooler__poolreset', options[:poolname])
|
892
|
+
break if pending
|
772
893
|
end
|
773
|
-
end
|
774
894
|
|
775
|
-
|
776
|
-
|
895
|
+
if options[:pending_vm]
|
896
|
+
pending_vm_count = redis.scard("vmpooler__pending__#{options[:poolname]}")
|
897
|
+
break unless pending_vm_count == 0
|
898
|
+
end
|
899
|
+
|
900
|
+
if options[:ondemand_request]
|
901
|
+
redis.multi
|
902
|
+
redis.zcard('vmpooler__provisioning__request')
|
903
|
+
redis.zcard('vmpooler__provisioning__processing')
|
904
|
+
redis.zcard('vmpooler__odcreate__task')
|
905
|
+
od_request, od_processing, od_createtask = redis.exec
|
906
|
+
break unless od_request == 0
|
907
|
+
break unless od_processing == 0
|
908
|
+
break unless od_createtask == 0
|
909
|
+
end
|
777
910
|
end
|
778
911
|
|
912
|
+
break if time_passed?(:exit_by, exit_by)
|
779
913
|
end
|
780
|
-
|
781
|
-
break if time_passed?(:exit_by, exit_by)
|
782
914
|
end
|
783
915
|
end
|
784
916
|
|
@@ -814,7 +946,7 @@ module Vmpooler
|
|
814
946
|
loop_delay = (loop_delay * loop_delay_decay).to_i
|
815
947
|
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
816
948
|
end
|
817
|
-
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true, clone_target_change: true, pool_reset: true)
|
949
|
+
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true, clone_target_change: true, pending_vm: true, pool_reset: true)
|
818
950
|
|
819
951
|
unless maxloop == 0
|
820
952
|
break if loop_count >= maxloop
|
@@ -844,77 +976,84 @@ module Vmpooler
|
|
844
976
|
end
|
845
977
|
|
846
978
|
def sync_pool_template(pool)
|
847
|
-
|
848
|
-
|
849
|
-
|
979
|
+
@redis.with_metrics do |redis|
|
980
|
+
pool_template = redis.hget('vmpooler__config__template', pool['name'])
|
981
|
+
if pool_template
|
982
|
+
pool['template'] = pool_template unless pool['template'] == pool_template
|
983
|
+
end
|
850
984
|
end
|
851
985
|
end
|
852
986
|
|
853
|
-
def prepare_template(pool, provider)
|
987
|
+
def prepare_template(pool, provider, redis)
|
854
988
|
if $config[:config]['create_template_delta_disks']
|
855
|
-
unless
|
989
|
+
unless redis.sismember('vmpooler__template__deltas', pool['template'])
|
856
990
|
begin
|
857
991
|
provider.create_template_delta_disks(pool)
|
858
|
-
|
992
|
+
redis.sadd('vmpooler__template__deltas', pool['template'])
|
859
993
|
rescue StandardError => e
|
860
994
|
$logger.log('s', "[!] [#{pool['name']}] failed while preparing a template with an error. As a result vmpooler could not create the template delta disks. Either a template delta disk already exists, or the template delta disk creation failed. The error is: #{e}")
|
861
995
|
end
|
862
996
|
end
|
863
997
|
end
|
864
|
-
|
998
|
+
redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
|
865
999
|
end
|
866
1000
|
|
867
1001
|
def evaluate_template(pool, provider)
|
868
1002
|
mutex = pool_mutex(pool['name'])
|
869
|
-
prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
|
870
|
-
configured_template = $redis.hget('vmpooler__config__template', pool['name'])
|
871
1003
|
return if mutex.locked?
|
872
1004
|
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
1005
|
+
catch :update_not_needed do
|
1006
|
+
@redis.with_metrics do |redis|
|
1007
|
+
prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
|
1008
|
+
configured_template = redis.hget('vmpooler__config__template', pool['name'])
|
1009
|
+
|
1010
|
+
if prepared_template.nil?
|
1011
|
+
mutex.synchronize do
|
1012
|
+
prepare_template(pool, provider, redis)
|
1013
|
+
prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
|
1014
|
+
end
|
1015
|
+
elsif prepared_template != pool['template']
|
1016
|
+
if configured_template.nil?
|
1017
|
+
mutex.synchronize do
|
1018
|
+
prepare_template(pool, provider, redis)
|
1019
|
+
prepared_template = redis.hget('vmpooler__template__prepared', pool['name'])
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
throw :update_not_needed if configured_template.nil?
|
1024
|
+
throw :update_not_needed if configured_template == prepared_template
|
1025
|
+
|
880
1026
|
mutex.synchronize do
|
881
|
-
|
882
|
-
prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
|
1027
|
+
update_pool_template(pool, provider, configured_template, prepared_template, redis)
|
883
1028
|
end
|
884
1029
|
end
|
885
1030
|
end
|
886
|
-
return if configured_template.nil?
|
887
|
-
return if configured_template == prepared_template
|
888
|
-
|
889
|
-
mutex.synchronize do
|
890
|
-
update_pool_template(pool, provider, configured_template, prepared_template)
|
891
|
-
end
|
892
1031
|
end
|
893
1032
|
|
894
|
-
def drain_pool(poolname)
|
1033
|
+
def drain_pool(poolname, redis)
|
895
1034
|
# Clear a pool of ready and pending instances
|
896
|
-
if
|
1035
|
+
if redis.scard("vmpooler__ready__#{poolname}") > 0
|
897
1036
|
$logger.log('s', "[*] [#{poolname}] removing ready instances")
|
898
|
-
|
899
|
-
move_vm_queue(poolname, vm, 'ready', 'completed')
|
1037
|
+
redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
|
1038
|
+
move_vm_queue(poolname, vm, 'ready', 'completed', redis)
|
900
1039
|
end
|
901
1040
|
end
|
902
|
-
if
|
1041
|
+
if redis.scard("vmpooler__pending__#{poolname}") > 0
|
903
1042
|
$logger.log('s', "[*] [#{poolname}] removing pending instances")
|
904
|
-
|
905
|
-
move_vm_queue(poolname, vm, 'pending', 'completed')
|
1043
|
+
redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
|
1044
|
+
move_vm_queue(poolname, vm, 'pending', 'completed', redis)
|
906
1045
|
end
|
907
1046
|
end
|
908
1047
|
end
|
909
1048
|
|
910
|
-
def update_pool_template(pool, provider, configured_template, prepared_template)
|
1049
|
+
def update_pool_template(pool, provider, configured_template, prepared_template, redis)
|
911
1050
|
pool['template'] = configured_template
|
912
1051
|
$logger.log('s', "[*] [#{pool['name']}] template updated from #{prepared_template} to #{configured_template}")
|
913
1052
|
# Remove all ready and pending VMs so new instances are created from the new template
|
914
|
-
drain_pool(pool['name'])
|
1053
|
+
drain_pool(pool['name'], redis)
|
915
1054
|
# Prepare template for deployment
|
916
1055
|
$logger.log('s', "[*] [#{pool['name']}] preparing pool template for deployment")
|
917
|
-
prepare_template(pool, provider)
|
1056
|
+
prepare_template(pool, provider, redis)
|
918
1057
|
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
919
1058
|
end
|
920
1059
|
|
@@ -922,38 +1061,45 @@ module Vmpooler
|
|
922
1061
|
mutex = pool_mutex(pool['name'])
|
923
1062
|
return if mutex.locked?
|
924
1063
|
|
925
|
-
|
926
|
-
|
927
|
-
|
1064
|
+
@redis.with_metrics do |redis|
|
1065
|
+
clone_target = redis.hget('vmpooler__config__clone_target', pool['name'])
|
1066
|
+
break if clone_target.nil?
|
1067
|
+
break if clone_target == pool['clone_target']
|
928
1068
|
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
1069
|
+
$logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
|
1070
|
+
mutex.synchronize do
|
1071
|
+
pool['clone_target'] = clone_target
|
1072
|
+
# Remove all ready and pending VMs so new instances are created for the new clone_target
|
1073
|
+
drain_pool(pool['name'], redis)
|
1074
|
+
end
|
1075
|
+
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
934
1076
|
end
|
935
|
-
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
936
1077
|
end
|
937
1078
|
|
938
1079
|
def remove_excess_vms(pool)
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
1080
|
+
@redis.with_metrics do |redis|
|
1081
|
+
redis.multi
|
1082
|
+
redis.scard("vmpooler__ready__#{pool['name']}")
|
1083
|
+
redis.scard("vmpooler__pending__#{pool['name']}")
|
1084
|
+
ready, pending = redis.exec
|
1085
|
+
total = pending.to_i + ready.to_i
|
1086
|
+
break if total.nil?
|
1087
|
+
break if total == 0
|
943
1088
|
|
944
|
-
|
945
|
-
|
946
|
-
|
1089
|
+
mutex = pool_mutex(pool['name'])
|
1090
|
+
break if mutex.locked?
|
1091
|
+
break unless ready.to_i > pool['size']
|
947
1092
|
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
1093
|
+
mutex.synchronize do
|
1094
|
+
difference = ready.to_i - pool['size']
|
1095
|
+
difference.times do
|
1096
|
+
next_vm = redis.spop("vmpooler__ready__#{pool['name']}")
|
1097
|
+
move_vm_queue(pool['name'], next_vm, 'ready', 'completed', redis)
|
1098
|
+
end
|
1099
|
+
if total > ready
|
1100
|
+
redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
|
1101
|
+
move_vm_queue(pool['name'], vm, 'pending', 'completed', redis)
|
1102
|
+
end
|
957
1103
|
end
|
958
1104
|
end
|
959
1105
|
end
|
@@ -963,26 +1109,30 @@ module Vmpooler
|
|
963
1109
|
mutex = pool_mutex(pool['name'])
|
964
1110
|
return if mutex.locked?
|
965
1111
|
|
966
|
-
|
967
|
-
|
1112
|
+
@redis.with_metrics do |redis|
|
1113
|
+
poolsize = redis.hget('vmpooler__config__poolsize', pool['name'])
|
1114
|
+
break if poolsize.nil?
|
968
1115
|
|
969
|
-
|
970
|
-
|
1116
|
+
poolsize = Integer(poolsize)
|
1117
|
+
break if poolsize == pool['size']
|
971
1118
|
|
972
|
-
|
973
|
-
|
1119
|
+
mutex.synchronize do
|
1120
|
+
pool['size'] = poolsize
|
1121
|
+
end
|
974
1122
|
end
|
975
1123
|
end
|
976
1124
|
|
977
1125
|
def reset_pool(pool)
|
978
1126
|
poolname = pool['name']
|
979
|
-
|
1127
|
+
@redis.with_metrics do |redis|
|
1128
|
+
break unless redis.sismember('vmpooler__poolreset', poolname)
|
980
1129
|
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
1130
|
+
redis.srem('vmpooler__poolreset', poolname)
|
1131
|
+
mutex = pool_mutex(poolname)
|
1132
|
+
mutex.synchronize do
|
1133
|
+
drain_pool(poolname, redis)
|
1134
|
+
$logger.log('s', "[*] [#{poolname}] reset has cleared ready and pending instances")
|
1135
|
+
end
|
986
1136
|
end
|
987
1137
|
end
|
988
1138
|
|
@@ -991,21 +1141,23 @@ module Vmpooler
|
|
991
1141
|
begin
|
992
1142
|
mutex = pool_mutex(pool['name'])
|
993
1143
|
mutex.synchronize do
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1144
|
+
@redis.with_metrics do |redis|
|
1145
|
+
provider.vms_in_pool(pool['name']).each do |vm|
|
1146
|
+
if !redis.sismember('vmpooler__running__' + pool['name'], vm['name']) &&
|
1147
|
+
!redis.sismember('vmpooler__ready__' + pool['name'], vm['name']) &&
|
1148
|
+
!redis.sismember('vmpooler__pending__' + pool['name'], vm['name']) &&
|
1149
|
+
!redis.sismember('vmpooler__completed__' + pool['name'], vm['name']) &&
|
1150
|
+
!redis.sismember('vmpooler__discovered__' + pool['name'], vm['name']) &&
|
1151
|
+
!redis.sismember('vmpooler__migrating__' + pool['name'], vm['name'])
|
1152
|
+
|
1153
|
+
pool_check_response[:discovered_vms] += 1
|
1154
|
+
redis.sadd('vmpooler__discovered__' + pool['name'], vm['name'])
|
1155
|
+
|
1156
|
+
$logger.log('s', "[?] [#{pool['name']}] '#{vm['name']}' added to 'discovered' queue")
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
inventory[vm['name']] = 1
|
1006
1160
|
end
|
1007
|
-
|
1008
|
-
inventory[vm['name']] = 1
|
1009
1161
|
end
|
1010
1162
|
end
|
1011
1163
|
rescue StandardError => e
|
@@ -1016,96 +1168,112 @@ module Vmpooler
|
|
1016
1168
|
end
|
1017
1169
|
|
1018
1170
|
def check_running_pool_vms(pool_name, provider, pool_check_response, inventory)
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1171
|
+
@redis.with_metrics do |redis|
|
1172
|
+
redis.smembers("vmpooler__running__#{pool_name}").each do |vm|
|
1173
|
+
if inventory[vm]
|
1174
|
+
begin
|
1175
|
+
vm_lifetime = redis.hget('vmpooler__vm__' + vm, 'lifetime') || $config[:config]['vm_lifetime'] || 12
|
1176
|
+
pool_check_response[:checked_running_vms] += 1
|
1177
|
+
check_running_vm(vm, pool_name, vm_lifetime, provider)
|
1178
|
+
rescue StandardError => e
|
1179
|
+
$logger.log('d', "[!] [#{pool_name}] _check_pool with an error while evaluating running VMs: #{e}")
|
1180
|
+
end
|
1181
|
+
else
|
1182
|
+
move_vm_queue(pool_name, vm, 'running', 'completed', redis, 'is a running VM but is missing from inventory. Marking as completed.')
|
1027
1183
|
end
|
1028
|
-
else
|
1029
|
-
move_vm_queue(pool_name, vm, 'running', 'completed', 'is a running VM but is missing from inventory. Marking as completed.')
|
1030
1184
|
end
|
1031
1185
|
end
|
1032
1186
|
end
|
1033
1187
|
|
1034
|
-
def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1188
|
+
def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl)
|
1189
|
+
@redis.with_metrics do |redis|
|
1190
|
+
redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
|
1191
|
+
if inventory[vm]
|
1192
|
+
begin
|
1193
|
+
pool_check_response[:checked_ready_vms] += 1
|
1194
|
+
check_ready_vm(vm, pool_name, pool_ttl, provider)
|
1195
|
+
rescue StandardError => e
|
1196
|
+
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{e}")
|
1197
|
+
end
|
1198
|
+
else
|
1199
|
+
move_vm_queue(pool_name, vm, 'ready', 'completed', redis, 'is a ready VM but is missing from inventory. Marking as completed.')
|
1042
1200
|
end
|
1043
|
-
else
|
1044
|
-
move_vm_queue(pool_name, vm, 'ready', 'completed', 'is a ready VM but is missing from inventory. Marking as completed.')
|
1045
1201
|
end
|
1046
1202
|
end
|
1047
1203
|
end
|
1048
1204
|
|
1049
|
-
def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout
|
1205
|
+
def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout)
|
1050
1206
|
pool_timeout ||= $config[:config]['timeout'] || 15
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1207
|
+
@redis.with_metrics do |redis|
|
1208
|
+
redis.smembers("vmpooler__pending__#{pool_name}").reverse.each do |vm|
|
1209
|
+
if inventory[vm]
|
1210
|
+
begin
|
1211
|
+
pool_check_response[:checked_pending_vms] += 1
|
1212
|
+
check_pending_vm(vm, pool_name, pool_timeout, provider)
|
1213
|
+
rescue StandardError => e
|
1214
|
+
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating pending VMs: #{e}")
|
1215
|
+
end
|
1216
|
+
else
|
1217
|
+
fail_pending_vm(vm, pool_name, pool_timeout, redis, false)
|
1058
1218
|
end
|
1059
|
-
else
|
1060
|
-
fail_pending_vm(vm, pool_name, pool_timeout, false)
|
1061
1219
|
end
|
1062
1220
|
end
|
1063
1221
|
end
|
1064
1222
|
|
1065
1223
|
def check_completed_pool_vms(pool_name, provider, pool_check_response, inventory)
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1224
|
+
@redis.with_metrics do |redis|
|
1225
|
+
redis.smembers("vmpooler__completed__#{pool_name}").each do |vm|
|
1226
|
+
if inventory[vm]
|
1227
|
+
begin
|
1228
|
+
pool_check_response[:destroyed_vms] += 1
|
1229
|
+
destroy_vm(vm, pool_name, provider)
|
1230
|
+
rescue StandardError => e
|
1231
|
+
redis.pipelined do
|
1232
|
+
redis.srem("vmpooler__completed__#{pool_name}", vm)
|
1233
|
+
redis.hdel("vmpooler__active__#{pool_name}", vm)
|
1234
|
+
redis.del("vmpooler__vm__#{vm}")
|
1235
|
+
end
|
1236
|
+
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating completed VMs: #{e}")
|
1237
|
+
end
|
1238
|
+
else
|
1239
|
+
$logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
|
1240
|
+
redis.pipelined do
|
1241
|
+
redis.srem("vmpooler__completed__#{pool_name}", vm)
|
1242
|
+
redis.hdel("vmpooler__active__#{pool_name}", vm)
|
1243
|
+
redis.del("vmpooler__vm__#{vm}")
|
1244
|
+
end
|
1076
1245
|
end
|
1077
|
-
else
|
1078
|
-
$logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
|
1079
|
-
$redis.srem("vmpooler__completed__#{pool_name}", vm)
|
1080
|
-
$redis.hdel("vmpooler__active__#{pool_name}", vm)
|
1081
|
-
$redis.del("vmpooler__vm__#{vm}")
|
1082
1246
|
end
|
1083
1247
|
end
|
1084
1248
|
end
|
1085
1249
|
|
1086
1250
|
def check_discovered_pool_vms(pool_name)
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1251
|
+
@redis.with_metrics do |redis|
|
1252
|
+
redis.smembers("vmpooler__discovered__#{pool_name}").reverse.each do |vm|
|
1253
|
+
%w[pending ready running completed].each do |queue|
|
1254
|
+
if redis.sismember("vmpooler__#{queue}__#{pool_name}", vm)
|
1255
|
+
$logger.log('d', "[!] [#{pool_name}] '#{vm}' found in '#{queue}', removed from 'discovered' queue")
|
1256
|
+
redis.srem("vmpooler__discovered__#{pool_name}", vm)
|
1257
|
+
end
|
1092
1258
|
end
|
1093
|
-
end
|
1094
1259
|
|
1095
|
-
|
1260
|
+
redis.smove("vmpooler__discovered__#{pool_name}", "vmpooler__completed__#{pool_name}", vm) if redis.sismember("vmpooler__discovered__#{pool_name}", vm)
|
1261
|
+
end
|
1096
1262
|
end
|
1097
1263
|
rescue StandardError => e
|
1098
1264
|
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating discovered VMs: #{e}")
|
1099
1265
|
end
|
1100
1266
|
|
1101
1267
|
def check_migrating_pool_vms(pool_name, provider, pool_check_response, inventory)
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1268
|
+
@redis.with_metrics do |redis|
|
1269
|
+
redis.smembers("vmpooler__migrating__#{pool_name}").reverse.each do |vm|
|
1270
|
+
if inventory[vm]
|
1271
|
+
begin
|
1272
|
+
pool_check_response[:migrated_vms] += 1
|
1273
|
+
migrate_vm(vm, pool_name, provider)
|
1274
|
+
rescue StandardError => e
|
1275
|
+
$logger.log('s', "[x] [#{pool_name}] '#{vm}' failed to migrate: #{e}")
|
1276
|
+
end
|
1109
1277
|
end
|
1110
1278
|
end
|
1111
1279
|
end
|
@@ -1114,29 +1282,37 @@ module Vmpooler
|
|
1114
1282
|
def repopulate_pool_vms(pool_name, provider, pool_check_response, pool_size)
|
1115
1283
|
return if pool_mutex(pool_name).locked?
|
1116
1284
|
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
$
|
1127
|
-
|
1128
|
-
|
1285
|
+
@redis.with_metrics do |redis|
|
1286
|
+
redis.multi
|
1287
|
+
redis.scard("vmpooler__ready__#{pool_name}")
|
1288
|
+
redis.scard("vmpooler__pending__#{pool_name}")
|
1289
|
+
redis.scard("vmpooler__running__#{pool_name}")
|
1290
|
+
ready, pending, running = redis.exec
|
1291
|
+
total = pending.to_i + ready.to_i
|
1292
|
+
|
1293
|
+
$metrics.gauge("ready.#{pool_name}", ready)
|
1294
|
+
$metrics.gauge("running.#{pool_name}", running)
|
1295
|
+
|
1296
|
+
unless pool_size == 0
|
1297
|
+
if redis.get("vmpooler__empty__#{pool_name}")
|
1298
|
+
redis.del("vmpooler__empty__#{pool_name}") unless ready == 0
|
1299
|
+
elsif ready == 0
|
1300
|
+
redis.set("vmpooler__empty__#{pool_name}", 'true')
|
1301
|
+
$logger.log('s', "[!] [#{pool_name}] is empty")
|
1302
|
+
end
|
1303
|
+
end
|
1129
1304
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1305
|
+
(pool_size - total.to_i).times do
|
1306
|
+
if redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
|
1307
|
+
begin
|
1308
|
+
redis.incr('vmpooler__tasks__clone')
|
1309
|
+
pool_check_response[:cloned_vms] += 1
|
1310
|
+
clone_vm(pool_name, provider)
|
1311
|
+
rescue StandardError => e
|
1312
|
+
$logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}")
|
1313
|
+
redis.decr('vmpooler__tasks__clone')
|
1314
|
+
raise
|
1315
|
+
end
|
1140
1316
|
end
|
1141
1317
|
end
|
1142
1318
|
end
|
@@ -1161,7 +1337,7 @@ module Vmpooler
|
|
1161
1337
|
|
1162
1338
|
check_running_pool_vms(pool['name'], provider, pool_check_response, inventory)
|
1163
1339
|
|
1164
|
-
check_ready_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['ready_ttl'])
|
1340
|
+
check_ready_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['ready_ttl'] || $config[:config]['ready_ttl'])
|
1165
1341
|
|
1166
1342
|
check_pending_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['timeout'])
|
1167
1343
|
|
@@ -1204,23 +1380,203 @@ module Vmpooler
|
|
1204
1380
|
#
|
1205
1381
|
# returns an object Vmpooler::PoolManager::Provider::*
|
1206
1382
|
# or raises an error if the class does not exist
|
1207
|
-
def create_provider_object(config, logger, metrics, provider_class, provider_name, options)
|
1383
|
+
def create_provider_object(config, logger, metrics, redis_connection_pool, provider_class, provider_name, options)
|
1208
1384
|
provider_klass = Vmpooler::PoolManager::Provider
|
1209
1385
|
provider_klass.constants.each do |classname|
|
1210
1386
|
next unless classname.to_s.casecmp(provider_class) == 0
|
1211
1387
|
|
1212
|
-
return provider_klass.const_get(classname).new(config, logger, metrics, provider_name, options)
|
1388
|
+
return provider_klass.const_get(classname).new(config, logger, metrics, redis_connection_pool, provider_name, options)
|
1213
1389
|
end
|
1214
1390
|
raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if provider.nil?
|
1215
1391
|
end
|
1216
1392
|
|
1393
|
+
def check_ondemand_requests(maxloop = 0,
|
1394
|
+
loop_delay_min = CHECK_LOOP_DELAY_MIN_DEFAULT,
|
1395
|
+
loop_delay_max = CHECK_LOOP_DELAY_MAX_DEFAULT,
|
1396
|
+
loop_delay_decay = CHECK_LOOP_DELAY_DECAY_DEFAULT)
|
1397
|
+
|
1398
|
+
$logger.log('d', '[*] [ondemand_provisioner] starting worker thread')
|
1399
|
+
|
1400
|
+
$threads['ondemand_provisioner'] = Thread.new do
|
1401
|
+
_check_ondemand_requests(maxloop, loop_delay_min, loop_delay_max, loop_delay_decay)
|
1402
|
+
end
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
def _check_ondemand_requests(maxloop = 0,
|
1406
|
+
loop_delay_min = CHECK_LOOP_DELAY_MIN_DEFAULT,
|
1407
|
+
loop_delay_max = CHECK_LOOP_DELAY_MAX_DEFAULT,
|
1408
|
+
loop_delay_decay = CHECK_LOOP_DELAY_DECAY_DEFAULT)
|
1409
|
+
|
1410
|
+
loop_delay_min = $config[:config]['check_loop_delay_min'] unless $config[:config]['check_loop_delay_min'].nil?
|
1411
|
+
loop_delay_max = $config[:config]['check_loop_delay_max'] unless $config[:config]['check_loop_delay_max'].nil?
|
1412
|
+
loop_delay_decay = $config[:config]['check_loop_delay_decay'] unless $config[:config]['check_loop_delay_decay'].nil?
|
1413
|
+
|
1414
|
+
loop_delay_decay = 2.0 if loop_delay_decay <= 1.0
|
1415
|
+
loop_delay_max = loop_delay_min if loop_delay_max.nil? || loop_delay_max < loop_delay_min
|
1416
|
+
|
1417
|
+
loop_count = 1
|
1418
|
+
loop_delay = loop_delay_min
|
1419
|
+
|
1420
|
+
loop do
|
1421
|
+
result = process_ondemand_requests
|
1422
|
+
|
1423
|
+
loop_delay = (loop_delay * loop_delay_decay).to_i
|
1424
|
+
loop_delay = loop_delay_min if result > 0
|
1425
|
+
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
1426
|
+
sleep_with_wakeup_events(loop_delay, loop_delay_min, ondemand_request: true)
|
1427
|
+
|
1428
|
+
unless maxloop == 0
|
1429
|
+
break if loop_count >= maxloop
|
1430
|
+
|
1431
|
+
loop_count += 1
|
1432
|
+
end
|
1433
|
+
end
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
def process_ondemand_requests
|
1437
|
+
@redis.with_metrics do |redis|
|
1438
|
+
requests = redis.zrange('vmpooler__provisioning__request', 0, -1)
|
1439
|
+
requests&.map { |request_id| create_ondemand_vms(request_id, redis) }
|
1440
|
+
provisioning_tasks = process_ondemand_vms(redis)
|
1441
|
+
requests_ready = check_ondemand_requests_ready(redis)
|
1442
|
+
requests.length + provisioning_tasks + requests_ready
|
1443
|
+
end
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
def create_ondemand_vms(request_id, redis)
|
1447
|
+
requested = redis.hget("vmpooler__odrequest__#{request_id}", 'requested')
|
1448
|
+
unless requested
|
1449
|
+
$logger.log('s', "Failed to find odrequest for request_id '#{request_id}'")
|
1450
|
+
redis.zrem('vmpooler__provisioning__request', request_id)
|
1451
|
+
return
|
1452
|
+
end
|
1453
|
+
score = redis.zscore('vmpooler__provisioning__request', request_id)
|
1454
|
+
requested = requested.split(',')
|
1455
|
+
|
1456
|
+
redis.pipelined do
|
1457
|
+
requested.each do |request|
|
1458
|
+
redis.zadd('vmpooler__odcreate__task', Time.now.to_i, "#{request}:#{request_id}")
|
1459
|
+
end
|
1460
|
+
redis.zrem('vmpooler__provisioning__request', request_id)
|
1461
|
+
redis.zadd('vmpooler__provisioning__processing', score, request_id)
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
def process_ondemand_vms(redis)
|
1466
|
+
queue_key = 'vmpooler__odcreate__task'
|
1467
|
+
queue = redis.zrange(queue_key, 0, -1, with_scores: true)
|
1468
|
+
ondemand_clone_limit = $config[:config]['ondemand_clone_limit']
|
1469
|
+
queue.each do |request, score|
|
1470
|
+
clone_count = redis.get('vmpooler__tasks__ondemandclone').to_i
|
1471
|
+
break unless clone_count < ondemand_clone_limit
|
1472
|
+
|
1473
|
+
pool_alias, pool, count, request_id = request.split(':')
|
1474
|
+
count = count.to_i
|
1475
|
+
provider = get_provider_for_pool(pool)
|
1476
|
+
slots = ondemand_clone_limit - clone_count
|
1477
|
+
break if slots == 0
|
1478
|
+
|
1479
|
+
if slots >= count
|
1480
|
+
count.times do
|
1481
|
+
redis.incr('vmpooler__tasks__ondemandclone')
|
1482
|
+
clone_vm(pool, provider, request_id, pool_alias)
|
1483
|
+
end
|
1484
|
+
redis.zrem(queue_key, request)
|
1485
|
+
else
|
1486
|
+
remaining_count = count - slots
|
1487
|
+
slots.times do
|
1488
|
+
redis.incr('vmpooler__tasks__ondemandclone')
|
1489
|
+
clone_vm(pool, provider, request_id, pool_alias)
|
1490
|
+
end
|
1491
|
+
redis.pipelined do
|
1492
|
+
redis.zrem(queue_key, request)
|
1493
|
+
redis.zadd(queue_key, score, "#{pool_alias}:#{pool}:#{remaining_count}:#{request_id}")
|
1494
|
+
end
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
queue.length
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
def vms_ready?(request_id, redis)
|
1501
|
+
catch :request_not_ready do
|
1502
|
+
request_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
|
1503
|
+
requested_platforms = request_hash['requested'].split(',')
|
1504
|
+
requested_platforms.each do |platform|
|
1505
|
+
platform_alias, pool, count = platform.split(':')
|
1506
|
+
pools_filled = redis.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
1507
|
+
throw :request_not_ready unless pools_filled.to_i == count.to_i
|
1508
|
+
end
|
1509
|
+
return true
|
1510
|
+
end
|
1511
|
+
false
|
1512
|
+
end
|
1513
|
+
|
1514
|
+
def check_ondemand_requests_ready(redis)
|
1515
|
+
in_progress_requests = redis.zrange('vmpooler__provisioning__processing', 0, -1, with_scores: true)
|
1516
|
+
in_progress_requests&.each do |request_id, score|
|
1517
|
+
check_ondemand_request_ready(request_id, redis, score)
|
1518
|
+
end
|
1519
|
+
in_progress_requests.length
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
def check_ondemand_request_ready(request_id, redis, score = nil)
|
1523
|
+
# default expiration is one month to ensure the data does not stay in redis forever
|
1524
|
+
default_expiration = 259_200_0
|
1525
|
+
processing_key = 'vmpooler__provisioning__processing'
|
1526
|
+
ondemand_hash_key = "vmpooler__odrequest__#{request_id}"
|
1527
|
+
score ||= redis.zscore(processing_key, request_id)
|
1528
|
+
return if request_expired?(request_id, score, redis)
|
1529
|
+
|
1530
|
+
return unless vms_ready?(request_id, redis)
|
1531
|
+
|
1532
|
+
redis.multi
|
1533
|
+
redis.hset(ondemand_hash_key, 'status', 'ready')
|
1534
|
+
redis.expire(ondemand_hash_key, default_expiration)
|
1535
|
+
redis.zrem(processing_key, request_id)
|
1536
|
+
redis.exec
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
def request_expired?(request_id, score, redis)
|
1540
|
+
delta = Time.now.to_i - score.to_i
|
1541
|
+
ondemand_request_ttl = $config[:config]['ondemand_request_ttl']
|
1542
|
+
return false unless delta > ondemand_request_ttl * 60
|
1543
|
+
|
1544
|
+
$logger.log('s', "Ondemand request for '#{request_id}' failed to provision all instances within the configured ttl '#{ondemand_request_ttl}'")
|
1545
|
+
expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
|
1546
|
+
redis.pipelined do
|
1547
|
+
redis.zrem('vmpooler__provisioning__processing', request_id)
|
1548
|
+
redis.hset("vmpooler__odrequest__#{request_id}", 'status', 'failed')
|
1549
|
+
redis.expire("vmpooler__odrequest__#{request_id}", expiration_ttl)
|
1550
|
+
end
|
1551
|
+
remove_vms_for_failed_request(request_id, expiration_ttl, redis)
|
1552
|
+
true
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
def remove_vms_for_failed_request(request_id, expiration_ttl, redis)
|
1556
|
+
request_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
|
1557
|
+
requested_platforms = request_hash['requested'].split(',')
|
1558
|
+
requested_platforms.each do |platform|
|
1559
|
+
platform_alias, pool, _count = platform.split(':')
|
1560
|
+
pools_filled = redis.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
1561
|
+
redis.pipelined do
|
1562
|
+
pools_filled&.each do |vm|
|
1563
|
+
move_vm_queue(pool, vm, 'running', 'completed', redis, "moved to completed queue. '#{request_id}' could not be filled in time")
|
1564
|
+
end
|
1565
|
+
redis.expire("vmpooler__#{request_id}__#{platform_alias}__#{pool}", expiration_ttl)
|
1566
|
+
end
|
1567
|
+
end
|
1568
|
+
end
|
1569
|
+
|
1217
1570
|
def execute!(maxloop = 0, loop_delay = 1)
|
1218
1571
|
$logger.log('d', 'starting vmpooler')
|
1219
1572
|
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1573
|
+
@redis.with_metrics do |redis|
|
1574
|
+
# Clear out the tasks manager, as we don't know about any tasks at this point
|
1575
|
+
redis.set('vmpooler__tasks__clone', 0)
|
1576
|
+
redis.set('vmpooler__tasks__ondemandclone', 0)
|
1577
|
+
# Clear out vmpooler__migrations since stale entries may be left after a restart
|
1578
|
+
redis.del('vmpooler__migration')
|
1579
|
+
end
|
1224
1580
|
|
1225
1581
|
# Copy vSphere settings to correct location. This happens with older configuration files
|
1226
1582
|
if !$config[:vsphere].nil? && ($config[:providers].nil? || $config[:providers][:vsphere].nil?)
|
@@ -1270,7 +1626,7 @@ module Vmpooler
|
|
1270
1626
|
provider_class = $config[:providers][provider_name.to_sym]['provider_class']
|
1271
1627
|
end
|
1272
1628
|
begin
|
1273
|
-
$providers[provider_name] = create_provider_object($config, $logger, $metrics, provider_class, provider_name, {}) if $providers[provider_name].nil?
|
1629
|
+
$providers[provider_name] = create_provider_object($config, $logger, $metrics, @redis, provider_class, provider_name, {}) if $providers[provider_name].nil?
|
1274
1630
|
rescue StandardError => e
|
1275
1631
|
$logger.log('s', "Error while creating provider for pool #{pool['name']}: #{e}")
|
1276
1632
|
raise
|
@@ -1304,6 +1660,13 @@ module Vmpooler
|
|
1304
1660
|
end
|
1305
1661
|
end
|
1306
1662
|
|
1663
|
+
if !$threads['ondemand_provisioner']
|
1664
|
+
check_ondemand_requests
|
1665
|
+
elsif !$threads['ondemand_provisioner'].alive?
|
1666
|
+
$logger.log('d', '[!] [ondemand_provisioner] worker thread died, restarting')
|
1667
|
+
check_ondemand_requests(check_loop_delay_min, check_loop_delay_max, check_loop_delay_decay)
|
1668
|
+
end
|
1669
|
+
|
1307
1670
|
sleep(loop_delay)
|
1308
1671
|
|
1309
1672
|
unless maxloop == 0
|