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.
@@ -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
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
772
893
  end
773
- end
774
894
 
775
- if options[:pool_reset]
776
- break if $redis.sismember('vmpooler__poolreset', options[:poolname])
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
- pool_template = $redis.hget('vmpooler__config__template', pool['name'])
848
- if pool_template
849
- 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
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 $redis.sismember('vmpooler__template__deltas', pool['template'])
989
+ unless redis.sismember('vmpooler__template__deltas', pool['template'])
856
990
  begin
857
991
  provider.create_template_delta_disks(pool)
858
- $redis.sadd('vmpooler__template__deltas', pool['template'])
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
- $redis.hset('vmpooler__template__prepared', pool['name'], pool['template'])
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
- if prepared_template.nil?
874
- mutex.synchronize do
875
- prepare_template(pool, provider)
876
- prepared_template = $redis.hget('vmpooler__template__prepared', pool['name'])
877
- end
878
- elsif prepared_template != pool['template']
879
- 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
+
880
1026
  mutex.synchronize do
881
- prepare_template(pool, provider)
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 $redis.scard("vmpooler__ready__#{poolname}") > 0
1035
+ if redis.scard("vmpooler__ready__#{poolname}") > 0
897
1036
  $logger.log('s', "[*] [#{poolname}] removing ready instances")
898
- $redis.smembers("vmpooler__ready__#{poolname}").each do |vm|
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 $redis.scard("vmpooler__pending__#{poolname}") > 0
1041
+ if redis.scard("vmpooler__pending__#{poolname}") > 0
903
1042
  $logger.log('s', "[*] [#{poolname}] removing pending instances")
904
- $redis.smembers("vmpooler__pending__#{poolname}").each do |vm|
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
- clone_target = $redis.hget('vmpooler__config__clone_target', pool['name'])
926
- return if clone_target.nil?
927
- 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']
928
1068
 
929
- $logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
930
- mutex.synchronize do
931
- pool['clone_target'] = clone_target
932
- # Remove all ready and pending VMs so new instances are created for the new clone_target
933
- 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")
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
- ready = $redis.scard("vmpooler__ready__#{pool['name']}")
940
- total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
941
- return if total.nil?
942
- 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
943
1088
 
944
- mutex = pool_mutex(pool['name'])
945
- return if mutex.locked?
946
- return unless ready > pool['size']
1089
+ mutex = pool_mutex(pool['name'])
1090
+ break if mutex.locked?
1091
+ break unless ready.to_i > pool['size']
947
1092
 
948
- mutex.synchronize do
949
- difference = ready - pool['size']
950
- difference.times do
951
- next_vm = $redis.spop("vmpooler__ready__#{pool['name']}")
952
- move_vm_queue(pool['name'], next_vm, 'ready', 'completed')
953
- end
954
- if total > ready
955
- $redis.smembers("vmpooler__pending__#{pool['name']}").each do |vm|
956
- 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
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
- poolsize = $redis.hget('vmpooler__config__poolsize', pool['name'])
967
- return if poolsize.nil?
1112
+ @redis.with_metrics do |redis|
1113
+ poolsize = redis.hget('vmpooler__config__poolsize', pool['name'])
1114
+ break if poolsize.nil?
968
1115
 
969
- poolsize = Integer(poolsize)
970
- return if poolsize == pool['size']
1116
+ poolsize = Integer(poolsize)
1117
+ break if poolsize == pool['size']
971
1118
 
972
- mutex.synchronize do
973
- pool['size'] = poolsize
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
- return unless $redis.sismember('vmpooler__poolreset', poolname)
1127
+ @redis.with_metrics do |redis|
1128
+ break unless redis.sismember('vmpooler__poolreset', poolname)
980
1129
 
981
- $redis.srem('vmpooler__poolreset', poolname)
982
- mutex = pool_mutex(poolname)
983
- mutex.synchronize do
984
- drain_pool(poolname)
985
- $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
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
- provider.vms_in_pool(pool['name']).each do |vm|
995
- if !$redis.sismember('vmpooler__running__' + pool['name'], vm['name']) &&
996
- !$redis.sismember('vmpooler__ready__' + pool['name'], vm['name']) &&
997
- !$redis.sismember('vmpooler__pending__' + pool['name'], vm['name']) &&
998
- !$redis.sismember('vmpooler__completed__' + pool['name'], vm['name']) &&
999
- !$redis.sismember('vmpooler__discovered__' + pool['name'], vm['name']) &&
1000
- !$redis.sismember('vmpooler__migrating__' + pool['name'], vm['name'])
1001
-
1002
- pool_check_response[:discovered_vms] += 1
1003
- $redis.sadd('vmpooler__discovered__' + pool['name'], vm['name'])
1004
-
1005
- $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
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
- $redis.smembers("vmpooler__running__#{pool_name}").each do |vm|
1020
- if inventory[vm]
1021
- begin
1022
- vm_lifetime = $redis.hget('vmpooler__vm__' + vm, 'lifetime') || $config[:config]['vm_lifetime'] || 12
1023
- pool_check_response[:checked_running_vms] += 1
1024
- check_running_vm(vm, pool_name, vm_lifetime, provider)
1025
- rescue StandardError => e
1026
- $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.')
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 = 0)
1035
- $redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
1036
- if inventory[vm]
1037
- begin
1038
- pool_check_response[:checked_ready_vms] += 1
1039
- check_ready_vm(vm, pool_name, pool_ttl || 0, provider)
1040
- rescue StandardError => e
1041
- $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.')
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 = nil)
1205
+ def check_pending_pool_vms(pool_name, provider, pool_check_response, inventory, pool_timeout)
1050
1206
  pool_timeout ||= $config[:config]['timeout'] || 15
1051
- $redis.smembers("vmpooler__pending__#{pool_name}").reverse.each do |vm|
1052
- if inventory[vm]
1053
- begin
1054
- pool_check_response[:checked_pending_vms] += 1
1055
- check_pending_vm(vm, pool_name, pool_timeout, provider)
1056
- rescue StandardError => e
1057
- $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)
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
- $redis.smembers("vmpooler__completed__#{pool_name}").each do |vm|
1067
- if inventory[vm]
1068
- begin
1069
- pool_check_response[:destroyed_vms] += 1
1070
- destroy_vm(vm, pool_name, provider)
1071
- rescue StandardError => e
1072
- $redis.srem("vmpooler__completed__#{pool_name}", vm)
1073
- $redis.hdel("vmpooler__active__#{pool_name}", vm)
1074
- $redis.del("vmpooler__vm__#{vm}")
1075
- $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
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
- $redis.smembers("vmpooler__discovered__#{pool_name}").reverse.each do |vm|
1088
- %w[pending ready running completed].each do |queue|
1089
- if $redis.sismember("vmpooler__#{queue}__#{pool_name}", vm)
1090
- $logger.log('d', "[!] [#{pool_name}] '#{vm}' found in '#{queue}', removed from 'discovered' queue")
1091
- $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
1092
1258
  end
1093
- end
1094
1259
 
1095
- $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
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
- $redis.smembers("vmpooler__migrating__#{pool_name}").reverse.each do |vm|
1103
- if inventory[vm]
1104
- begin
1105
- pool_check_response[:migrated_vms] += 1
1106
- migrate_vm(vm, pool_name, provider)
1107
- rescue StandardError => e
1108
- $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
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
- ready = $redis.scard("vmpooler__ready__#{pool_name}")
1118
- total = $redis.scard("vmpooler__pending__#{pool_name}") + ready
1119
-
1120
- $metrics.gauge("ready.#{pool_name}", $redis.scard("vmpooler__ready__#{pool_name}"))
1121
- $metrics.gauge("running.#{pool_name}", $redis.scard("vmpooler__running__#{pool_name}"))
1122
-
1123
- if $redis.get("vmpooler__empty__#{pool_name}")
1124
- $redis.del("vmpooler__empty__#{pool_name}") unless ready == 0
1125
- elsif ready == 0
1126
- $redis.set("vmpooler__empty__#{pool_name}", 'true')
1127
- $logger.log('s', "[!] [#{pool_name}] is empty")
1128
- 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
1129
1304
 
1130
- (pool_size - total).times do
1131
- if $redis.get('vmpooler__tasks__clone').to_i < $config[:config]['task_limit'].to_i
1132
- begin
1133
- $redis.incr('vmpooler__tasks__clone')
1134
- pool_check_response[:cloned_vms] += 1
1135
- clone_vm(pool_name, provider)
1136
- rescue StandardError => e
1137
- $logger.log('s', "[!] [#{pool_name}] clone failed during check_pool with an error: #{e}")
1138
- $redis.decr('vmpooler__tasks__clone')
1139
- 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
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
- # Clear out the tasks manager, as we don't know about any tasks at this point
1221
- $redis.set('vmpooler__tasks__clone', 0)
1222
- # Clear out vmpooler__migrations since stale entries may be left after a restart
1223
- $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
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