vmpooler 0.11.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,35 +15,17 @@ module Vmpooler
15
15
  @metric_prefix = 'connectionpool' if @metric_prefix.nil? || @metric_prefix == ''
16
16
  end
17
17
 
18
- if Thread.respond_to?(:handle_interrupt)
19
- # MRI
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
- yield conn
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, redis, metrics)
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
- # Connect to Redis
22
- $redis = redis
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
- previously_configured_pools = $redis.smembers('vmpooler__pools')
49
- currently_configured_pools = []
50
- config[:pools].each do |pool|
51
- currently_configured_pools << pool['name']
52
- $redis.sadd('vmpooler__pools', pool['name'])
53
- pool_keys = pool.keys
54
- pool_keys.delete('alias')
55
- to_set = {}
56
- pool_keys.each do |k|
57
- to_set[k] = pool[k]
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
- to_set['alias'] = pool['alias'].join(',') if to_set.key?('alias')
60
- $redis.hmset("vmpooler__pool__#{pool['name']}", to_set.to_a.flatten) unless to_set.empty?
61
- end
62
- previously_configured_pools.each do |pool|
63
- unless currently_configured_pools.include? pool
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
- fail_pending_vm(vm, pool, timeout)
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
- if provider.vm_ready?(pool, vm)
90
- move_pending_vm_to_ready(vm, pool)
91
- else
92
- fail_pending_vm(vm, pool, timeout)
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
- $redis.srem("vmpooler__pending__#{pool}", vm)
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 = $redis.hget("vmpooler__vm__#{vm}", 'clone')
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
- $redis.smove('vmpooler__pending__' + pool, 'vmpooler__completed__' + pool, vm)
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 = $redis.hget('vmpooler__vm__' + vm, 'clone')
124
- finish = format('%<time>.2f', time: Time.now - Time.parse(clone_time)) if 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
- $redis.smove('vmpooler__pending__' + pool, 'vmpooler__ready__' + pool, vm)
127
- $redis.hset('vmpooler__boot__' + Date.today.to_s, pool + ':' + vm, finish) # maybe remove as this is never used by vmpooler itself?
128
- $redis.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
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
- # last boot time is displayed in API, and used by alarming script
131
- $redis.hset('vmpooler__lastboot', pool, Time.now)
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
- check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check')
164
- return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime'])
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
- $redis.hset('vmpooler__vm__' + vm, 'check', Time.now)
167
- # Check if the hosts TTL has expired
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 = $redis.hget("vmpooler__vm__#{vm}", 'ready')
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 ((Time.now - boottime) / 60).to_s[/^\d+\.\d{1}/].to_f > ttl
177
- $redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
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
- return if mismatched_hostname?(vm, pool_name, provider)
222
+ break if mismatched_hostname?(vm, pool_name, provider, redis)
185
223
 
186
- vm_still_ready?(pool_name, vm, provider)
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 = $redis.hget("vmpooler__vm__#{vm}", 'ready')
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
- $redis.smove('vmpooler__ready__' + pool_name, 'vmpooler__completed__' + pool_name, vm)
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
- # Check that VM is within defined lifetime
238
- checkouttime = $redis.hget('vmpooler__active__' + pool, vm)
239
- if checkouttime
240
- running = (Time.now - Time.parse(checkouttime)) / 60 / 60
241
-
242
- if (ttl.to_i > 0) && (running.to_i >= ttl.to_i)
243
- move_vm_queue(pool, vm, 'running', 'completed', "reached end of TTL after #{ttl} hours")
244
- return
245
- end
246
- end
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
- if provider.vm_ready?(pool, vm)
249
- return
250
- else
251
- host = provider.get_vm(pool, vm)
290
+ if provider.vm_ready?(pool, vm)
291
+ throw :stop_checking
292
+ else
293
+ host = provider.get_vm(pool, vm)
252
294
 
253
- if host
254
- return
255
- else
256
- move_vm_queue(pool, vm, 'running', 'completed', 'is no longer in inventory, removing from running')
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
- $redis.smove("vmpooler__#{queue_from}__#{pool}", "vmpooler__#{queue_to}__#{pool}", vm)
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
- $logger.log('s', "[!] [#{pool_name}] failed while cloning VM with an error: #{e}")
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(_pool_name)
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
- noun = @name_generator.noun(max: 10)
289
- adjective = @name_generator.adjective(max: 14 - noun.length)
290
- random_name = [adjective, noun].join('-')
291
- hostname = $config[:config]['prefix'] + random_name
292
- available = $redis.hlen('vmpooler__vm__' + hostname) == 0
293
-
294
- [hostname, available]
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, available = generate_and_check_hostname(pool_name)
302
- break if available
356
+ hostname, hostname_available = generate_and_check_hostname(pool_name)
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
- $metrics.increment("errors.duplicatehostname.#{pool_name}")
306
- $logger.log('s', "[!] [#{pool_name}] Generated hostname #{hostname} was not unique (attempt \##{hostname_retries} of #{max_hostname_retries})")
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 available
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 _clone_vm(pool_name, provider)
315
- new_vmname = find_unique_hostname(pool_name)
316
-
317
- # Add VM to Redis inventory ('pending' pool)
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
- $logger.log('d', "[ ] [#{pool_name}] Starting to clone '#{new_vmname}'")
324
- start = Time.now
325
- provider.create_vm(pool_name, new_vmname)
326
- finish = format('%<time>.2f', time: Time.now - start)
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
- $redis.hset('vmpooler__clone__' + Date.today.to_s, pool_name + ':' + new_vmname, finish)
329
- $redis.hset('vmpooler__vm__' + new_vmname, 'clone_time', finish)
330
- $logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds")
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
- $metrics.timing("clone.#{pool_name}", finish)
333
- rescue StandardError
334
- $redis.srem("vmpooler__pending__#{pool_name}", new_vmname)
335
- expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
336
- $redis.expire("vmpooler__vm__#{new_vmname}", expiration_ttl)
337
- raise
338
- ensure
339
- $redis.decr('vmpooler__tasks__clone')
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
- $redis.hdel('vmpooler__active__' + pool, vm)
361
- $redis.hset('vmpooler__vm__' + vm, 'destroy', Time.now)
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
- # Auto-expire metadata key
364
- $redis.expire('vmpooler__vm__' + vm, ($config[:redis]['data_ttl'].to_i * 60 * 60))
462
+ # Auto-expire metadata key
463
+ redis.expire('vmpooler__vm__' + vm, ($config[:redis]['data_ttl'].to_i * 60 * 60))
464
+ end
365
465
 
366
- start = Time.now
466
+ start = Time.now
367
467
 
368
- provider.destroy_vm(pool, vm)
468
+ provider.destroy_vm(pool, vm)
369
469
 
370
- $redis.srem('vmpooler__completed__' + pool, vm)
470
+ redis.srem('vmpooler__completed__' + pool, vm)
371
471
 
372
- finish = format('%<time>.2f', time: Time.now - start)
373
- $logger.log('s', "[-] [#{pool}] '#{vm}' destroyed in #{finish} seconds")
374
- $metrics.timing("destroy.#{pool}", finish)
375
- get_vm_usage_labels(vm)
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
- checkout = $redis.hget("vmpooler__vm__#{vm}", 'checkout')
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
- jenkins_build_url = $redis.hget("vmpooler__vm__#{vm}", 'tag:jenkins_build_url')
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
- rdisks = $redis.hget('vmpooler__vm__' + vm_name, 'disk')
510
- disks = rdisks ? rdisks.split(':') : []
511
- disks.push("+#{disk_size}gb")
512
- $redis.hset('vmpooler__vm__' + vm_name, 'disk', disks.join(':'))
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
- $redis.hset('vmpooler__vm__' + vm_name, 'snapshot:' + snapshot_name, Time.now.to_s)
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
- $redis.hget('vmpooler__vm__' + vm_name, 'template')
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
- task_detail = $redis.spop('vmpooler__tasks__disk')
633
- unless task_detail.nil?
634
- begin
635
- vm_name, disk_size = task_detail.split(':')
636
- pool_name = get_pool_name_for_vm(vm_name)
637
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
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
- provider = get_provider_for_pool(pool_name)
640
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
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
- create_vm_disk(pool_name, vm_name, disk_size, provider)
643
- rescue StandardError => e
644
- $logger.log('s', "[!] [disk_manager] disk creation appears to have failed: #{e}")
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
- task_detail = $redis.spop('vmpooler__tasks__snapshot')
778
+ @redis.with_metrics do |redis|
779
+ task_detail = redis.spop('vmpooler__tasks__snapshot')
669
780
 
670
- unless task_detail.nil?
671
- begin
672
- vm_name, snapshot_name = task_detail.split(':')
673
- pool_name = get_pool_name_for_vm(vm_name)
674
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
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
- provider = get_provider_for_pool(pool_name)
677
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
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
- create_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
680
- rescue StandardError => e
681
- $logger.log('s', "[!] [snapshot_manager] snapshot create appears to have failed: #{e}")
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
- task_detail = $redis.spop('vmpooler__tasks__snapshot-revert')
796
+ task_detail = redis.spop('vmpooler__tasks__snapshot-revert')
686
797
 
687
- unless task_detail.nil?
688
- begin
689
- vm_name, snapshot_name = task_detail.split(':')
690
- pool_name = get_pool_name_for_vm(vm_name)
691
- raise("Unable to determine which pool #{vm_name} is a member of") if pool_name.nil?
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
- provider = get_provider_for_pool(pool_name)
694
- raise("Missing Provider for vm #{vm_name} in pool #{pool_name}") if provider.nil?
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
- revert_vm_snapshot(pool_name, vm_name, snapshot_name, provider)
697
- rescue StandardError => e
698
- $logger.log('s', "[!] [snapshot_manager] snapshot revert appears to have failed: #{e}")
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
- $redis.srem("vmpooler__migrating__#{pool_name}", vm_name)
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
- initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}") if options[:pool_size_change]
855
+ @redis.with_metrics do |redis|
856
+ initial_ready_size = redis.scard("vmpooler__ready__#{options[:poolname]}") if options[:pool_size_change]
742
857
 
743
- initial_clone_target = $redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target]) if options[:clone_target_change]
858
+ initial_clone_target = redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target]) if options[:clone_target_change]
744
859
 
745
- initial_template = $redis.hget('vmpooler__template__prepared', options[:poolname]) if options[:pool_template_change]
860
+ initial_template = redis.hget('vmpooler__template__prepared', options[:poolname]) if options[:pool_template_change]
746
861
 
747
- loop do
748
- sleep(1)
749
- break if time_passed?(:exit_by, exit_by)
862
+ loop do
863
+ sleep(1)
864
+ break if time_passed?(:exit_by, exit_by)
750
865
 
751
- # Check for wakeup events
752
- if time_passed?(:wakeup_by, wakeup_by)
753
- wakeup_by = Time.now + wakeup_period
866
+ # Check for wakeup events
867
+ if time_passed?(:wakeup_by, wakeup_by)
868
+ wakeup_by = Time.now + wakeup_period
754
869
 
755
- # Wakeup if the number of ready VMs has changed
756
- if options[:pool_size_change]
757
- ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
758
- break unless ready_size == initial_ready_size
759
- end
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
- if options[:clone_target_change]
762
- clone_target = $redis.hget('vmpooler__config__clone_target}', options[:poolname])
763
- if clone_target
764
- break unless clone_target == initial_clone_target
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
- if options[:pool_template_change]
769
- configured_template = $redis.hget('vmpooler__config__template', options[:poolname])
770
- if configured_template
771
- break unless initial_template == configured_template
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
893
+ end
894
+
895
+ if options[:pending_vm]
896
+ pending_vm_count = redis.scard("vmpooler__pending__#{options[:poolname]}")
897
+ break unless pending_vm_count == 0
772
898
  end
773
- end
774
899
 
775
- if options[:pool_reset]
776
- break if $redis.sismember('vmpooler__poolreset', options[:poolname])
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
@@ -822,6 +954,8 @@ module Vmpooler
822
954
  loop_count += 1
823
955
  end
824
956
  end
957
+ rescue Redis::CannotConnectError
958
+ raise
825
959
  rescue StandardError => e
826
960
  $logger.log('s', "[!] [#{pool['name']}] Error while checking the pool: #{e}")
827
961
  raise
@@ -842,77 +976,84 @@ module Vmpooler
842
976
  end
843
977
 
844
978
  def sync_pool_template(pool)
845
- pool_template = $redis.hget('vmpooler__config__template', pool['name'])
846
- if pool_template
847
- pool['template'] = pool_template unless pool['template'] == pool_template
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
848
984
  end
849
985
  end
850
986
 
851
- def prepare_template(pool, provider)
987
+ def prepare_template(pool, provider, redis)
852
988
  if $config[:config]['create_template_delta_disks']
853
- unless $redis.sismember('vmpooler__template__deltas', pool['template'])
989
+ unless redis.sismember('vmpooler__template__deltas', pool['template'])
854
990
  begin
855
991
  provider.create_template_delta_disks(pool)
856
- $redis.sadd('vmpooler__template__deltas', pool['template'])
992
+ redis.sadd('vmpooler__template__deltas', pool['template'])
857
993
  rescue StandardError => e
858
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}")
859
995
  end
860
996
  end
861
997
  end
862
- $redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
998
+ redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
863
999
  end
864
1000
 
865
1001
  def evaluate_template(pool, provider)
866
1002
  mutex = pool_mutex(pool['name'])
867
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
868
- configured_template = $redis.hget('vmpooler__config__template', pool['name'])
869
1003
  return if mutex.locked?
870
1004
 
871
- if prepared_template.nil?
872
- mutex.synchronize do
873
- prepare_template(pool, provider)
874
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
875
- end
876
- elsif prepared_template != pool['template']
877
- if configured_template.nil?
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
+
878
1026
  mutex.synchronize do
879
- prepare_template(pool, provider)
880
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
1027
+ update_pool_template(pool, provider, configured_template, prepared_template, redis)
881
1028
  end
882
1029
  end
883
1030
  end
884
- return if configured_template.nil?
885
- return if configured_template == prepared_template
886
-
887
- mutex.synchronize do
888
- update_pool_template(pool, provider, configured_template, prepared_template)
889
- end
890
1031
  end
891
1032
 
892
- def drain_pool(poolname)
1033
+ def drain_pool(poolname, redis)
893
1034
  # Clear a pool of ready and pending instances
894
- if $redis.scard("vmpooler__ready__#{poolname}") > 0
1035
+ if redis.scard("vmpooler__ready__#{poolname}") > 0
895
1036
  $logger.log('s', "[*] [#{poolname}] removing ready instances")
896
- $redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
897
- 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)
898
1039
  end
899
1040
  end
900
- if $redis.scard("vmpooler__pending__#{poolname}") > 0
1041
+ if redis.scard("vmpooler__pending__#{poolname}") > 0
901
1042
  $logger.log('s', "[*] [#{poolname}] removing pending instances")
902
- $redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
903
- 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)
904
1045
  end
905
1046
  end
906
1047
  end
907
1048
 
908
- def update_pool_template(pool, provider, configured_template, prepared_template)
1049
+ def update_pool_template(pool, provider, configured_template, prepared_template, redis)
909
1050
  pool['template'] = configured_template
910
1051
  $logger.log('s', "[*] [#{pool['name']}] template updated from #{prepared_template} to #{configured_template}")
911
1052
  # Remove all ready and pending VMs so new instances are created from the new template
912
- drain_pool(pool['name'])
1053
+ drain_pool(pool['name'], redis)
913
1054
  # Prepare template for deployment
914
1055
  $logger.log('s', "[*] [#{pool['name']}] preparing pool template for deployment")
915
- prepare_template(pool, provider)
1056
+ prepare_template(pool, provider, redis)
916
1057
  $logger.log('s', "[*] [#{pool['name']}] is ready for use")
917
1058
  end
918
1059
 
@@ -920,38 +1061,45 @@ module Vmpooler
920
1061
  mutex = pool_mutex(pool['name'])
921
1062
  return if mutex.locked?
922
1063
 
923
- clone_target = $redis.hget('vmpooler__config__clone_target', pool['name'])
924
- return if clone_target.nil?
925
- return if clone_target == pool['clone_target']
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']
926
1068
 
927
- $logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
928
- mutex.synchronize do
929
- pool['clone_target'] = clone_target
930
- # Remove all ready and pending VMs so new instances are created for the new clone_target
931
- drain_pool(pool['name'])
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")
932
1076
  end
933
- $logger.log('s', "[*] [#{pool['name']}] is ready for use")
934
1077
  end
935
1078
 
936
1079
  def remove_excess_vms(pool)
937
- ready = $redis.scard("vmpooler__ready__#{pool['name']}")
938
- total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
939
- return if total.nil?
940
- return if total == 0
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
941
1088
 
942
- mutex = pool_mutex(pool['name'])
943
- return if mutex.locked?
944
- return unless ready > pool['size']
1089
+ mutex = pool_mutex(pool['name'])
1090
+ break if mutex.locked?
1091
+ break unless ready.to_i > pool['size']
945
1092
 
946
- mutex.synchronize do
947
- difference = ready - pool['size']
948
- difference.times do
949
- next_vm = $redis.spop("vmpooler__ready__#{pool['name']}")
950
- move_vm_queue(pool['name'], next_vm, 'ready', 'completed')
951
- end
952
- if total > ready
953
- $redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
954
- move_vm_queue(pool['name'], vm, 'pending', 'completed')
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
955
1103
  end
956
1104
  end
957
1105
  end
@@ -961,26 +1109,30 @@ module Vmpooler
961
1109
  mutex = pool_mutex(pool['name'])
962
1110
  return if mutex.locked?
963
1111
 
964
- poolsize = $redis.hget('vmpooler__config__poolsize', pool['name'])
965
- return if poolsize.nil?
1112
+ @redis.with_metrics do |redis|
1113
+ poolsize = redis.hget('vmpooler__config__poolsize', pool['name'])
1114
+ break if poolsize.nil?
966
1115
 
967
- poolsize = Integer(poolsize)
968
- return if poolsize == pool['size']
1116
+ poolsize = Integer(poolsize)
1117
+ break if poolsize == pool['size']
969
1118
 
970
- mutex.synchronize do
971
- pool['size'] = poolsize
1119
+ mutex.synchronize do
1120
+ pool['size'] = poolsize
1121
+ end
972
1122
  end
973
1123
  end
974
1124
 
975
1125
  def reset_pool(pool)
976
1126
  poolname = pool['name']
977
- return unless $redis.sismember('vmpooler__poolreset', poolname)
1127
+ @redis.with_metrics do |redis|
1128
+ break unless redis.sismember('vmpooler__poolreset', poolname)
978
1129
 
979
- $redis.srem('vmpooler__poolreset', poolname)
980
- mutex = pool_mutex(poolname)
981
- mutex.synchronize do
982
- drain_pool(poolname)
983
- $logger.log('s', "[*] [#{poolname}] reset has cleared ready and pending instances")
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
984
1136
  end
985
1137
  end
986
1138
 
@@ -989,21 +1141,23 @@ module Vmpooler
989
1141
  begin
990
1142
  mutex = pool_mutex(pool['name'])
991
1143
  mutex.synchronize do
992
- provider.vms_in_pool(pool['name']).each do |vm|
993
- if !$redis.sismember('vmpooler__running__' + pool['name'], vm['name']) &&
994
- !$redis.sismember('vmpooler__ready__' + pool['name'], vm['name']) &&
995
- !$redis.sismember('vmpooler__pending__' + pool['name'], vm['name']) &&
996
- !$redis.sismember('vmpooler__completed__' + pool['name'], vm['name']) &&
997
- !$redis.sismember('vmpooler__discovered__' + pool['name'], vm['name']) &&
998
- !$redis.sismember('vmpooler__migrating__' + pool['name'], vm['name'])
999
-
1000
- pool_check_response[:discovered_vms] += 1
1001
- $redis.sadd('vmpooler__discovered__' + pool['name'], vm['name'])
1002
-
1003
- $logger.log('s', "[?] [#{pool['name']}] '#{vm['name']}' added to 'discovered' queue")
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
1004
1160
  end
1005
-
1006
- inventory[vm['name']] = 1
1007
1161
  end
1008
1162
  end
1009
1163
  rescue StandardError => e
@@ -1014,96 +1168,112 @@ module Vmpooler
1014
1168
  end
1015
1169
 
1016
1170
  def check_running_pool_vms(pool_name, provider, pool_check_response, inventory)
1017
- $redis.smembers("vmpooler__running__#{pool_name}").each do |vm|
1018
- if inventory[vm]
1019
- begin
1020
- vm_lifetime = $redis.hget('vmpooler__vm__' + vm, 'lifetime') || $config[:config]['vm_lifetime'] || 12
1021
- pool_check_response[:checked_running_vms] += 1
1022
- check_running_vm(vm, pool_name, vm_lifetime, provider)
1023
- rescue StandardError => e
1024
- $logger.log('d', "[!] [#{pool_name}] _check_pool with an error while evaluating running VMs: #{e}")
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.')
1025
1183
  end
1026
- else
1027
- move_vm_queue(pool_name, vm, 'running', 'completed', 'is a running VM but is missing from inventory. Marking as completed.')
1028
1184
  end
1029
1185
  end
1030
1186
  end
1031
1187
 
1032
- def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl = 0)
1033
- $redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
1034
- if inventory[vm]
1035
- begin
1036
- pool_check_response[:checked_ready_vms] += 1
1037
- check_ready_vm(vm, pool_name, pool_ttl || 0, provider)
1038
- rescue StandardError => e
1039
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{e}")
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.')
1040
1200
  end
1041
- else
1042
- move_vm_queue(pool_name, vm, 'ready', 'completed', 'is a ready VM but is missing from inventory. Marking as completed.')
1043
1201
  end
1044
1202
  end
1045
1203
  end
1046
1204
 
1047
- def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout = nil)
1205
+ def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout)
1048
1206
  pool_timeout ||= $config[:config]['timeout'] || 15
1049
- $redis.smembers("vmpooler__pending__#{pool_name}").reverse.each do |vm|
1050
- if inventory[vm]
1051
- begin
1052
- pool_check_response[:checked_pending_vms] += 1
1053
- check_pending_vm(vm, pool_name, pool_timeout, provider)
1054
- rescue StandardError => e
1055
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating pending VMs: #{e}")
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)
1056
1218
  end
1057
- else
1058
- fail_pending_vm(vm, pool_name, pool_timeout, false)
1059
1219
  end
1060
1220
  end
1061
1221
  end
1062
1222
 
1063
1223
  def check_completed_pool_vms(pool_name, provider, pool_check_response, inventory)
1064
- $redis.smembers("vmpooler__completed__#{pool_name}").each do |vm|
1065
- if inventory[vm]
1066
- begin
1067
- pool_check_response[:destroyed_vms] += 1
1068
- destroy_vm(vm, pool_name, provider)
1069
- rescue StandardError => e
1070
- $redis.srem("vmpooler__completed__#{pool_name}", vm)
1071
- $redis.hdel("vmpooler__active__#{pool_name}", vm)
1072
- $redis.del("vmpooler__vm__#{vm}")
1073
- $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating completed VMs: #{e}")
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
1074
1245
  end
1075
- else
1076
- $logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
1077
- $redis.srem("vmpooler__completed__#{pool_name}", vm)
1078
- $redis.hdel("vmpooler__active__#{pool_name}", vm)
1079
- $redis.del("vmpooler__vm__#{vm}")
1080
1246
  end
1081
1247
  end
1082
1248
  end
1083
1249
 
1084
1250
  def check_discovered_pool_vms(pool_name)
1085
- $redis.smembers("vmpooler__discovered__#{pool_name}").reverse.each do |vm|
1086
- %w[pending ready running completed].each do |queue|
1087
- if $redis.sismember("vmpooler__#{queue}__#{pool_name}", vm)
1088
- $logger.log('d', "[!] [#{pool_name}] '#{vm}' found in '#{queue}', removed from 'discovered' queue")
1089
- $redis.srem("vmpooler__discovered__#{pool_name}", vm)
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
1090
1258
  end
1091
- end
1092
1259
 
1093
- $redis.smove("vmpooler__discovered__#{pool_name}", "vmpooler__completed__#{pool_name}", vm) if $redis.sismember("vmpooler__discovered__#{pool_name}", vm)
1260
+ redis.smove("vmpooler__discovered__#{pool_name}", "vmpooler__completed__#{pool_name}", vm) if redis.sismember("vmpooler__discovered__#{pool_name}", vm)
1261
+ end
1094
1262
  end
1095
1263
  rescue StandardError => e
1096
1264
  $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating discovered VMs: #{e}")
1097
1265
  end
1098
1266
 
1099
1267
  def check_migrating_pool_vms(pool_name, provider, pool_check_response, inventory)
1100
- $redis.smembers("vmpooler__migrating__#{pool_name}").reverse.each do |vm|
1101
- if inventory[vm]
1102
- begin
1103
- pool_check_response[:migrated_vms] += 1
1104
- migrate_vm(vm, pool_name, provider)
1105
- rescue StandardError => e
1106
- $logger.log('s', "[x] [#{pool_name}] '#{vm}' failed to migrate: #{e}")
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
1107
1277
  end
1108
1278
  end
1109
1279
  end
@@ -1112,29 +1282,37 @@ module Vmpooler
1112
1282
  def repopulate_pool_vms(pool_name, provider, pool_check_response, pool_size)
1113
1283
  return if pool_mutex(pool_name).locked?
1114
1284
 
1115
- ready = $redis.scard("vmpooler__ready__#{pool_name}")
1116
- total = $redis.scard("vmpooler__pending__#{pool_name}") + ready
1117
-
1118
- $metrics.gauge("ready.#{pool_name}", $redis.scard("vmpooler__ready__#{pool_name}"))
1119
- $metrics.gauge("running.#{pool_name}", $redis.scard("vmpooler__running__#{pool_name}"))
1120
-
1121
- if $redis.get("vmpooler__empty__#{pool_name}")
1122
- $redis.del("vmpooler__empty__#{pool_name}") unless ready == 0
1123
- elsif ready == 0
1124
- $redis.set("vmpooler__empty__#{pool_name}", 'true')
1125
- $logger.log('s', "[!] [#{pool_name}] is empty")
1126
- end
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
1127
1304
 
1128
- (pool_size - total).times do
1129
- if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
1130
- begin
1131
- $redis.incr('vmpooler__tasks__clone')
1132
- pool_check_response[:cloned_vms] += 1
1133
- clone_vm(pool_name, provider)
1134
- rescue StandardError => e
1135
- $logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}")
1136
- $redis.decr('vmpooler__tasks__clone')
1137
- raise
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
1138
1316
  end
1139
1317
  end
1140
1318
  end
@@ -1159,7 +1337,7 @@ module Vmpooler
1159
1337
 
1160
1338
  check_running_pool_vms(pool['name'], provider, pool_check_response, inventory)
1161
1339
 
1162
- 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'])
1163
1341
 
1164
1342
  check_pending_pool_vms(pool['name'], provider, pool_check_response, inventory, pool['timeout'])
1165
1343
 
@@ -1202,23 +1380,203 @@ module Vmpooler
1202
1380
  #
1203
1381
  # returns an object Vmpooler::PoolManager::Provider::*
1204
1382
  # or raises an error if the class does not exist
1205
- 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)
1206
1384
  provider_klass = Vmpooler::PoolManager::Provider
1207
1385
  provider_klass.constants.each do |classname|
1208
1386
  next unless classname.to_s.casecmp(provider_class) == 0
1209
1387
 
1210
- 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)
1211
1389
  end
1212
1390
  raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if provider.nil?
1213
1391
  end
1214
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
+
1215
1570
  def execute!(maxloop = 0, loop_delay = 1)
1216
1571
  $logger.log('d', 'starting vmpooler')
1217
1572
 
1218
- # Clear out the tasks manager, as we don't know about any tasks at this point
1219
- $redis.set('vmpooler__tasks__clone', 0)
1220
- # Clear out vmpooler__migrations since stale entries may be left after a restart
1221
- $redis.del('vmpooler__migration')
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
1222
1580
 
1223
1581
  # Copy vSphere settings to correct location. This happens with older configuration files
1224
1582
  if !$config[:vsphere].nil? && ($config[:providers].nil? || $config[:providers][:vsphere].nil?)
@@ -1268,7 +1626,7 @@ module Vmpooler
1268
1626
  provider_class = $config[:providers][provider_name.to_sym]['provider_class']
1269
1627
  end
1270
1628
  begin
1271
- $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?
1272
1630
  rescue StandardError => e
1273
1631
  $logger.log('s', "Error while creating provider for pool #{pool['name']}: #{e}")
1274
1632
  raise
@@ -1302,6 +1660,13 @@ module Vmpooler
1302
1660
  end
1303
1661
  end
1304
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
+
1305
1670
  sleep(loop_delay)
1306
1671
 
1307
1672
  unless maxloop == 0
@@ -1310,6 +1675,9 @@ module Vmpooler
1310
1675
  loop_count += 1
1311
1676
  end
1312
1677
  end
1678
+ rescue Redis::CannotConnectError => e
1679
+ $logger.log('s', "Cannot connect to the redis server: #{e}")
1680
+ raise
1313
1681
  end
1314
1682
  end
1315
1683
  end