vmpooler 0.11.3 → 0.13.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,35 +15,17 @@ module Vmpooler
15
15
  @metric_prefix = 'connectionpool' if @metric_prefix.nil? || @metric_prefix == ''
16
16
  end
17
17
 
18
- if Thread.respond_to?(:handle_interrupt)
19
- # MRI
20
- def with_metrics(options = {})
21
- Thread.handle_interrupt(Exception => :never) do
22
- start = Time.now
23
- conn = checkout(options)
24
- timespan_ms = ((Time.now - start) * 1000).to_i
25
- @metrics&.gauge(@metric_prefix + '.available', @available.length)
26
- @metrics&.timing(@metric_prefix + '.waited', timespan_ms)
27
- begin
28
- Thread.handle_interrupt(Exception => :immediate) do
29
- yield conn
30
- end
31
- ensure
32
- checkin
33
- @metrics&.gauge(@metric_prefix + '.available', @available.length)
34
- end
35
- end
36
- end
37
- else
38
- # jruby 1.7.x
39
- def with_metrics(options = {})
18
+ def with_metrics(options = {})
19
+ Thread.handle_interrupt(Exception => :never) do
40
20
  start = Time.now
41
21
  conn = checkout(options)
42
22
  timespan_ms = ((Time.now - start) * 1000).to_i
43
23
  @metrics&.gauge(@metric_prefix + '.available', @available.length)
44
24
  @metrics&.timing(@metric_prefix + '.waited', timespan_ms)
45
25
  begin
46
- yield conn
26
+ Thread.handle_interrupt(Exception => :immediate) do
27
+ yield conn
28
+ end
47
29
  ensure
48
30
  checkin
49
31
  @metrics&.gauge(@metric_prefix + '.available', @available.length)
@@ -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