vmpooler 0.11.3 → 0.13.3

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