vmpooler 2.1.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/vmpooler/api/helpers.rb +365 -299
- data/lib/vmpooler/api/reroute.rb +16 -0
- data/lib/vmpooler/api/v1.rb +501 -378
- data/lib/vmpooler/api/v2.rb +505 -0
- data/lib/vmpooler/api.rb +2 -1
- data/lib/vmpooler/pool_manager.rb +84 -63
- data/lib/vmpooler/providers/base.rb +1 -1
- data/lib/vmpooler/util/parsing.rb +21 -1
- data/lib/vmpooler/version.rb +1 -1
- data/lib/vmpooler.rb +6 -1
- metadata +103 -83
@@ -60,6 +60,7 @@ module Vmpooler
|
|
60
60
|
to_set[k] = pool[k]
|
61
61
|
end
|
62
62
|
to_set['alias'] = pool['alias'].join(',') if to_set.key?('alias')
|
63
|
+
to_set['domain'] = Parsing.get_domain_for_pool(config, pool['name'])
|
63
64
|
redis.hmset("vmpooler__pool__#{pool['name']}", to_set.to_a.flatten) unless to_set.empty?
|
64
65
|
end
|
65
66
|
previously_configured_pools.each do |pool|
|
@@ -118,7 +119,13 @@ module Vmpooler
|
|
118
119
|
pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias') if request_id
|
119
120
|
redis.multi
|
120
121
|
redis.smove("vmpooler__pending__#{pool}", "vmpooler__completed__#{pool}", vm)
|
121
|
-
|
122
|
+
if request_id
|
123
|
+
ondemandrequest_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
|
124
|
+
if ondemandrequest_hash && ondemandrequest_hash['status'] != 'failed' && ondemandrequest_hash['status'] != 'deleted'
|
125
|
+
# will retry a VM that did not come up as vm_ready? only if it has not been market failed or deleted
|
126
|
+
redis.zadd('vmpooler__odcreate__task', 1, "#{pool_alias}:#{pool}:1:#{request_id}")
|
127
|
+
end
|
128
|
+
end
|
122
129
|
redis.exec
|
123
130
|
$metrics.increment("errors.markedasfailed.#{pool}")
|
124
131
|
$logger.log('d', "[!] [#{pool}] '#{vm}' marked as 'failed' after #{timeout} minutes")
|
@@ -148,15 +155,15 @@ module Vmpooler
|
|
148
155
|
end
|
149
156
|
pool_alias = redis.hget("vmpooler__vm__#{vm}", 'pool_alias')
|
150
157
|
|
151
|
-
redis.pipelined do
|
152
|
-
|
153
|
-
|
158
|
+
redis.pipelined do |pipeline|
|
159
|
+
pipeline.hset("vmpooler__active__#{pool}", vm, Time.now)
|
160
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
|
154
161
|
if ondemandrequest_hash['token:token']
|
155
|
-
|
156
|
-
|
157
|
-
|
162
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'token:token', ondemandrequest_hash['token:token'])
|
163
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'token:user', ondemandrequest_hash['token:user'])
|
164
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'lifetime', $config[:config]['vm_lifetime_auth'].to_i)
|
158
165
|
end
|
159
|
-
|
166
|
+
pipeline.sadd("vmpooler__#{request_id}__#{pool_alias}__#{pool}", vm)
|
160
167
|
end
|
161
168
|
move_vm_queue(pool, vm, 'pending', 'running', redis)
|
162
169
|
check_ondemand_request_ready(request_id, redis)
|
@@ -164,12 +171,12 @@ module Vmpooler
|
|
164
171
|
redis.smove("vmpooler__pending__#{pool}", "vmpooler__ready__#{pool}", vm)
|
165
172
|
end
|
166
173
|
|
167
|
-
redis.pipelined do
|
168
|
-
|
169
|
-
|
174
|
+
redis.pipelined do |pipeline|
|
175
|
+
pipeline.hset("vmpooler__boot__#{Date.today}", "#{pool}:#{vm}", finish) # maybe remove as this is never used by vmpooler itself?
|
176
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'ready', Time.now)
|
170
177
|
|
171
178
|
# last boot time is displayed in API, and used by alarming script
|
172
|
-
|
179
|
+
pipeline.hset('vmpooler__lastboot', pool, Time.now)
|
173
180
|
end
|
174
181
|
|
175
182
|
$metrics.timing("time_to_ready_state.#{pool}", finish)
|
@@ -361,35 +368,47 @@ module Vmpooler
|
|
361
368
|
max_hostname_retries = 3
|
362
369
|
while hostname_retries < max_hostname_retries
|
363
370
|
hostname, hostname_available = generate_and_check_hostname
|
364
|
-
domain =
|
365
|
-
|
371
|
+
domain = Parsing.get_domain_for_pool(config, pool_name)
|
372
|
+
if domain
|
373
|
+
fqdn = "#{hostname}.#{domain}"
|
374
|
+
else
|
375
|
+
fqdn = hostname
|
376
|
+
end
|
377
|
+
|
378
|
+
# skip dns check if the provider is set to skip_dns_check_before_creating_vm
|
379
|
+
provider = get_provider_for_pool(pool_name)
|
380
|
+
if provider && provider.provider_config['skip_dns_check_before_creating_vm']
|
381
|
+
dns_available = true
|
382
|
+
else
|
383
|
+
dns_ip, dns_available = check_dns_available(fqdn)
|
384
|
+
end
|
385
|
+
|
366
386
|
break if hostname_available && dns_available
|
367
387
|
|
368
388
|
hostname_retries += 1
|
369
389
|
|
370
390
|
if !hostname_available
|
371
391
|
$metrics.increment("errors.duplicatehostname.#{pool_name}")
|
372
|
-
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{
|
392
|
+
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{fqdn} was not unique (attempt \##{hostname_retries} of #{max_hostname_retries})")
|
373
393
|
elsif !dns_available
|
374
394
|
$metrics.increment("errors.staledns.#{pool_name}")
|
375
|
-
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{
|
395
|
+
$logger.log('s', "[!] [#{pool_name}] Generated hostname #{fqdn} already exists in DNS records (#{dns_ip}), stale DNS")
|
376
396
|
end
|
377
397
|
end
|
378
398
|
|
379
|
-
raise "Unable to generate a unique hostname after #{hostname_retries} attempts. The last hostname checked was #{
|
399
|
+
raise "Unable to generate a unique hostname after #{hostname_retries} attempts. The last hostname checked was #{fqdn}" unless hostname_available && dns_available
|
380
400
|
|
381
401
|
hostname
|
382
402
|
end
|
383
403
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
vm_name = "#{vm_name}.#{domain}" if domain
|
404
|
+
# Query the DNS for the name we want to create and if it already exists, mark it unavailable
|
405
|
+
# This protects against stale DNS records
|
406
|
+
def check_dns_available(vm_name)
|
388
407
|
begin
|
389
408
|
dns_ip = Resolv.getaddress(vm_name)
|
390
409
|
rescue Resolv::ResolvError
|
391
410
|
# this is the expected case, swallow the error
|
392
|
-
# eg "no address for blah-daisy"
|
411
|
+
# eg "no address for blah-daisy.example.com"
|
393
412
|
return ['', true]
|
394
413
|
end
|
395
414
|
[dns_ip, false]
|
@@ -397,6 +416,7 @@ module Vmpooler
|
|
397
416
|
|
398
417
|
def _clone_vm(pool_name, provider, request_id = nil, pool_alias = nil)
|
399
418
|
new_vmname = find_unique_hostname(pool_name)
|
419
|
+
pool_domain = Parsing.get_domain_for_pool(config, pool_name)
|
400
420
|
mutex = vm_mutex(new_vmname)
|
401
421
|
mutex.synchronize do
|
402
422
|
@redis.with_metrics do |redis|
|
@@ -406,6 +426,7 @@ module Vmpooler
|
|
406
426
|
redis.hset("vmpooler__vm__#{new_vmname}", 'clone', Time.now)
|
407
427
|
redis.hset("vmpooler__vm__#{new_vmname}", 'template', pool_name) # This value is used to represent the pool.
|
408
428
|
redis.hset("vmpooler__vm__#{new_vmname}", 'pool', pool_name)
|
429
|
+
redis.hset("vmpooler__vm__#{new_vmname}", 'domain', pool_domain) if pool_domain
|
409
430
|
redis.hset("vmpooler__vm__#{new_vmname}", 'request_id', request_id) if request_id
|
410
431
|
redis.hset("vmpooler__vm__#{new_vmname}", 'pool_alias', pool_alias) if pool_alias
|
411
432
|
redis.exec
|
@@ -418,9 +439,9 @@ module Vmpooler
|
|
418
439
|
finish = format('%<time>.2f', time: Time.now - start)
|
419
440
|
|
420
441
|
@redis.with_metrics do |redis|
|
421
|
-
redis.pipelined do
|
422
|
-
|
423
|
-
|
442
|
+
redis.pipelined do |pipeline|
|
443
|
+
pipeline.hset("vmpooler__clone__#{Date.today}", "#{pool_name}:#{new_vmname}", finish)
|
444
|
+
pipeline.hset("vmpooler__vm__#{new_vmname}", 'clone_time', finish)
|
424
445
|
end
|
425
446
|
end
|
426
447
|
$logger.log('s', "[+] [#{pool_name}] '#{new_vmname}' cloned in #{finish} seconds")
|
@@ -428,10 +449,10 @@ module Vmpooler
|
|
428
449
|
$metrics.timing("clone.#{pool_name}", finish)
|
429
450
|
rescue StandardError
|
430
451
|
@redis.with_metrics do |redis|
|
431
|
-
redis.pipelined do
|
432
|
-
|
452
|
+
redis.pipelined do |pipeline|
|
453
|
+
pipeline.srem("vmpooler__pending__#{pool_name}", new_vmname)
|
433
454
|
expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
|
434
|
-
|
455
|
+
pipeline.expire("vmpooler__vm__#{new_vmname}", expiration_ttl)
|
435
456
|
end
|
436
457
|
end
|
437
458
|
raise
|
@@ -462,12 +483,12 @@ module Vmpooler
|
|
462
483
|
|
463
484
|
mutex.synchronize do
|
464
485
|
@redis.with_metrics do |redis|
|
465
|
-
redis.pipelined do
|
466
|
-
|
467
|
-
|
486
|
+
redis.pipelined do |pipeline|
|
487
|
+
pipeline.hdel("vmpooler__active__#{pool}", vm)
|
488
|
+
pipeline.hset("vmpooler__vm__#{vm}", 'destroy', Time.now)
|
468
489
|
|
469
490
|
# Auto-expire metadata key
|
470
|
-
|
491
|
+
pipeline.expire("vmpooler__vm__#{vm}", ($config[:redis]['data_ttl'].to_i * 60 * 60))
|
471
492
|
end
|
472
493
|
|
473
494
|
start = Time.now
|
@@ -879,7 +900,7 @@ module Vmpooler
|
|
879
900
|
loop_count = 1
|
880
901
|
loop_delay = loop_delay_min
|
881
902
|
provider = get_provider_for_pool(pool['name'])
|
882
|
-
raise("Could not find provider '#{pool['provider']}") if provider.nil?
|
903
|
+
raise("Could not find provider '#{pool['provider']}'") if provider.nil?
|
883
904
|
|
884
905
|
sync_pool_template(pool)
|
885
906
|
loop do
|
@@ -1204,19 +1225,19 @@ module Vmpooler
|
|
1204
1225
|
pool_check_response[:destroyed_vms] += 1
|
1205
1226
|
destroy_vm(vm, pool_name, provider)
|
1206
1227
|
rescue StandardError => e
|
1207
|
-
redis.pipelined do
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1228
|
+
redis.pipelined do |pipeline|
|
1229
|
+
pipeline.srem("vmpooler__completed__#{pool_name}", vm)
|
1230
|
+
pipeline.hdel("vmpooler__active__#{pool_name}", vm)
|
1231
|
+
pipeline.del("vmpooler__vm__#{vm}")
|
1211
1232
|
end
|
1212
1233
|
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating completed VMs: #{e}")
|
1213
1234
|
end
|
1214
1235
|
else
|
1215
1236
|
$logger.log('s', "[!] [#{pool_name}] '#{vm}' not found in inventory, removed from 'completed' queue")
|
1216
|
-
redis.pipelined do
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1237
|
+
redis.pipelined do |pipeline|
|
1238
|
+
pipeline.srem("vmpooler__completed__#{pool_name}", vm)
|
1239
|
+
pipeline.hdel("vmpooler__active__#{pool_name}", vm)
|
1240
|
+
pipeline.del("vmpooler__vm__#{vm}")
|
1220
1241
|
end
|
1221
1242
|
end
|
1222
1243
|
end
|
@@ -1366,7 +1387,7 @@ module Vmpooler
|
|
1366
1387
|
|
1367
1388
|
return provider_klass.const_get(classname).new(config, logger, metrics, redis_connection_pool, provider_name, options)
|
1368
1389
|
end
|
1369
|
-
raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if
|
1390
|
+
raise("Provider '#{provider_class}' is unknown for pool with provider name '#{provider_name}'") if provider_klass.nil?
|
1370
1391
|
end
|
1371
1392
|
|
1372
1393
|
def check_ondemand_requests(maxloop = 0,
|
@@ -1432,12 +1453,12 @@ module Vmpooler
|
|
1432
1453
|
score = redis.zscore('vmpooler__provisioning__request', request_id)
|
1433
1454
|
requested = requested.split(',')
|
1434
1455
|
|
1435
|
-
redis.pipelined do
|
1456
|
+
redis.pipelined do |pipeline|
|
1436
1457
|
requested.each do |request|
|
1437
|
-
|
1458
|
+
pipeline.zadd('vmpooler__odcreate__task', Time.now.to_i, "#{request}:#{request_id}")
|
1438
1459
|
end
|
1439
|
-
|
1440
|
-
|
1460
|
+
pipeline.zrem('vmpooler__provisioning__request', request_id)
|
1461
|
+
pipeline.zadd('vmpooler__provisioning__processing', score, request_id)
|
1441
1462
|
end
|
1442
1463
|
end
|
1443
1464
|
|
@@ -1467,9 +1488,9 @@ module Vmpooler
|
|
1467
1488
|
redis.incr('vmpooler__tasks__ondemandclone')
|
1468
1489
|
clone_vm(pool, provider, request_id, pool_alias)
|
1469
1490
|
end
|
1470
|
-
redis.pipelined do
|
1471
|
-
|
1472
|
-
|
1491
|
+
redis.pipelined do |pipeline|
|
1492
|
+
pipeline.zrem(queue_key, request)
|
1493
|
+
pipeline.zadd(queue_key, score, "#{pool_alias}:#{pool}:#{remaining_count}:#{request_id}")
|
1473
1494
|
end
|
1474
1495
|
end
|
1475
1496
|
end
|
@@ -1520,10 +1541,10 @@ module Vmpooler
|
|
1520
1541
|
|
1521
1542
|
$logger.log('s', "Ondemand request for '#{request_id}' failed to provision all instances within the configured ttl '#{ondemand_request_ttl}'")
|
1522
1543
|
expiration_ttl = $config[:redis]['data_ttl'].to_i * 60 * 60
|
1523
|
-
redis.pipelined do
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1544
|
+
redis.pipelined do |pipeline|
|
1545
|
+
pipeline.zrem('vmpooler__provisioning__processing', request_id)
|
1546
|
+
pipeline.hset("vmpooler__odrequest__#{request_id}", 'status', 'failed')
|
1547
|
+
pipeline.expire("vmpooler__odrequest__#{request_id}", expiration_ttl)
|
1527
1548
|
end
|
1528
1549
|
remove_vms_for_failed_request(request_id, expiration_ttl, redis)
|
1529
1550
|
true
|
@@ -1533,11 +1554,11 @@ module Vmpooler
|
|
1533
1554
|
request_hash = redis.hgetall("vmpooler__odrequest__#{request_id}")
|
1534
1555
|
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
|
1535
1556
|
pools_filled = redis.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
1536
|
-
redis.pipelined do
|
1557
|
+
redis.pipelined do |pipeline|
|
1537
1558
|
pools_filled&.each do |vm|
|
1538
|
-
move_vm_queue(pool, vm, 'running', 'completed',
|
1559
|
+
move_vm_queue(pool, vm, 'running', 'completed', pipeline, "moved to completed queue. '#{request_id}' could not be filled in time")
|
1539
1560
|
end
|
1540
|
-
|
1561
|
+
pipeline.expire("vmpooler__#{request_id}__#{platform_alias}__#{pool}", expiration_ttl)
|
1541
1562
|
end
|
1542
1563
|
end
|
1543
1564
|
end
|
@@ -1581,19 +1602,19 @@ module Vmpooler
|
|
1581
1602
|
$config[:pools].each do |pool|
|
1582
1603
|
provider_name = pool['provider']
|
1583
1604
|
# The provider_class parameter can be defined in the provider's data eg
|
1584
|
-
|
1585
|
-
#
|
1586
|
-
#
|
1587
|
-
#
|
1588
|
-
#
|
1605
|
+
# :providers:
|
1606
|
+
# :vsphere:
|
1607
|
+
# provider_class: 'vsphere'
|
1608
|
+
# :another-vsphere:
|
1609
|
+
# provider_class: 'vsphere'
|
1589
1610
|
# the above would create two providers/vsphere.rb class objects named 'vsphere' and 'another-vsphere'
|
1590
1611
|
# each pools would then define which provider definition to use: vsphere or another-vsphere
|
1591
1612
|
#
|
1592
1613
|
# if provider_class is not defined it will try to use the provider_name as the class, this is to be
|
1593
1614
|
# backwards compatible for example when there is only one provider listed
|
1594
1615
|
# :providers:
|
1595
|
-
#
|
1596
|
-
#
|
1616
|
+
# :dummy:
|
1617
|
+
# filename: 'db.txs'
|
1597
1618
|
# the above example would create an object based on the class providers/dummy.rb
|
1598
1619
|
if $config[:providers].nil? || $config[:providers][provider_name.to_sym].nil? || $config[:providers][provider_name.to_sym]['provider_class'].nil?
|
1599
1620
|
provider_class = provider_name
|
@@ -242,7 +242,7 @@ module Vmpooler
|
|
242
242
|
# returns
|
243
243
|
# nil when successful. Raises error when encountered
|
244
244
|
def create_template_delta_disks(_pool)
|
245
|
-
|
245
|
+
puts("#{self.class.name} does not implement create_template_delta_disks")
|
246
246
|
end
|
247
247
|
|
248
248
|
# inputs
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# utility class shared between apps
|
3
|
+
# utility class shared between apps api and pool_manager
|
4
4
|
module Vmpooler
|
5
5
|
class Parsing
|
6
6
|
def self.get_platform_pool_count(requested, &_block)
|
@@ -12,5 +12,25 @@ module Vmpooler
|
|
12
12
|
yield platform_alias, pool, count
|
13
13
|
end
|
14
14
|
end
|
15
|
+
|
16
|
+
# @param config [String] - the full config structure
|
17
|
+
# @param pool_name [String] - the name of the pool
|
18
|
+
# @return [String] - domain name for pool, if set in the provider for the pool or in the config block
|
19
|
+
def self.get_domain_for_pool(config, pool_name)
|
20
|
+
pool = config[:pools].find { |p| p['name'] == pool_name }
|
21
|
+
return nil unless pool
|
22
|
+
|
23
|
+
provider_name = pool.fetch('provider', 'vsphere') # see vmpooler.yaml.example where it states defaulting to vsphere
|
24
|
+
|
25
|
+
if config[:providers] && config[:providers][provider_name.to_sym] && config[:providers][provider_name.to_sym]['domain']
|
26
|
+
domain = config[:providers][provider_name.to_sym]['domain']
|
27
|
+
elsif config[:config] && config[:config]['domain']
|
28
|
+
domain = config[:config]['domain']
|
29
|
+
else
|
30
|
+
domain = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
domain
|
34
|
+
end
|
15
35
|
end
|
16
36
|
end
|
data/lib/vmpooler/version.rb
CHANGED
data/lib/vmpooler.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
module Vmpooler
|
4
4
|
require 'concurrent'
|
5
5
|
require 'date'
|
6
|
+
require 'deep_merge'
|
6
7
|
require 'json'
|
7
8
|
require 'net/ldap'
|
8
9
|
require 'open-uri'
|
@@ -16,6 +17,7 @@ module Vmpooler
|
|
16
17
|
|
17
18
|
# Dependencies for tracing
|
18
19
|
require 'opentelemetry-instrumentation-concurrent_ruby'
|
20
|
+
require 'opentelemetry-instrumentation-http_client'
|
19
21
|
require 'opentelemetry-instrumentation-redis'
|
20
22
|
require 'opentelemetry-instrumentation-sinatra'
|
21
23
|
require 'opentelemetry-sdk'
|
@@ -41,8 +43,9 @@ module Vmpooler
|
|
41
43
|
if parsed_config[:config]['extra_config']
|
42
44
|
extra_configs = parsed_config[:config]['extra_config'].split(',')
|
43
45
|
extra_configs.each do |config|
|
46
|
+
puts "loading extra_config file #{config}"
|
44
47
|
extra_config = YAML.load_file(config)
|
45
|
-
parsed_config.
|
48
|
+
parsed_config.deep_merge(extra_config)
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
@@ -131,6 +134,7 @@ module Vmpooler
|
|
131
134
|
# Create an index of pool aliases
|
132
135
|
parsed_config[:pool_names] = Set.new
|
133
136
|
unless parsed_config[:pools]
|
137
|
+
puts 'loading pools configuration from redis, since the config[:pools] is empty'
|
134
138
|
redis = new_redis(parsed_config[:redis]['server'], parsed_config[:redis]['port'], parsed_config[:redis]['password'])
|
135
139
|
parsed_config[:pools] = load_pools_from_redis(redis)
|
136
140
|
end
|
@@ -264,6 +268,7 @@ module Vmpooler
|
|
264
268
|
OpenTelemetry::SDK.configure do |c|
|
265
269
|
c.use 'OpenTelemetry::Instrumentation::Sinatra'
|
266
270
|
c.use 'OpenTelemetry::Instrumentation::ConcurrentRuby'
|
271
|
+
c.use 'OpenTelemetry::Instrumentation::HttpClient'
|
267
272
|
c.use 'OpenTelemetry::Instrumentation::Redis'
|
268
273
|
|
269
274
|
c.add_span_processor(span_processor)
|