vmpooler 2.1.0 → 2.4.0
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.
- 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)
|