vmpooler 0.12.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/vmpooler +18 -11
- data/lib/vmpooler.rb +26 -16
- data/lib/vmpooler/api.rb +41 -34
- data/lib/vmpooler/api/helpers.rb +2 -2
- data/lib/vmpooler/api/request_logger.rb +20 -0
- data/lib/vmpooler/api/v1.rb +302 -21
- data/lib/vmpooler/generic_connection_pool.rb +12 -28
- data/lib/vmpooler/metrics.rb +24 -0
- data/lib/vmpooler/metrics/dummy_statsd.rb +24 -0
- data/lib/vmpooler/metrics/graphite.rb +47 -0
- data/lib/vmpooler/metrics/promstats.rb +380 -0
- data/lib/vmpooler/metrics/promstats/collector_middleware.rb +121 -0
- data/lib/vmpooler/metrics/statsd.rb +40 -0
- data/lib/vmpooler/pool_manager.rb +763 -409
- data/lib/vmpooler/providers/base.rb +2 -1
- data/lib/vmpooler/providers/dummy.rb +4 -3
- data/lib/vmpooler/providers/vsphere.rb +137 -54
- data/lib/vmpooler/util/parsing.rb +16 -0
- data/lib/vmpooler/version.rb +1 -1
- metadata +39 -6
- data/lib/vmpooler/dummy_statsd.rb +0 -22
- data/lib/vmpooler/graphite.rb +0 -42
- data/lib/vmpooler/statsd.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74df51cf1d525f0bc7afe2da930e68d0b2e1e014e10d184213556e4a13bd170b
|
4
|
+
data.tar.gz: 7cfe6d26ebb99e421f2b9daeabe1dfcb610cc8457cae1f79c5632df33eb6d426
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4e00e1cf43c08652b6186a741f048a487c1300744a2a40912759bdf06a2ad306fd59ae9adb10364be191047edc4cc84d8ea99fbf211806d59228b0993e61d0f
|
7
|
+
data.tar.gz: 4948f3405e5969bcd55b350604d1a2b8bfa9270960b635c4448a9478764f79bef1b2af101e11a3389587cdc2bd0fb22f28c1b7d1582227414aa42764d8451a74
|
data/bin/vmpooler
CHANGED
@@ -7,36 +7,43 @@ config = Vmpooler.config
|
|
7
7
|
redis_host = config[:redis]['server']
|
8
8
|
redis_port = config[:redis]['port']
|
9
9
|
redis_password = config[:redis]['password']
|
10
|
+
redis_connection_pool_size = config[:redis]['connection_pool_size']
|
11
|
+
redis_connection_pool_timeout = config[:redis]['connection_pool_timeout']
|
10
12
|
logger_file = config[:config]['logfile']
|
11
13
|
|
12
|
-
|
14
|
+
logger = Vmpooler::Logger.new logger_file
|
15
|
+
metrics = Vmpooler::Metrics.init(logger, config)
|
13
16
|
|
14
17
|
torun_threads = []
|
15
18
|
if ARGV.count == 0
|
16
|
-
torun = [
|
19
|
+
torun = %i[api manager]
|
17
20
|
else
|
18
21
|
torun = []
|
19
|
-
torun <<
|
20
|
-
torun <<
|
22
|
+
torun << :api if ARGV.include? 'api'
|
23
|
+
torun << :manager if ARGV.include? 'manager'
|
21
24
|
exit(2) if torun.empty?
|
22
25
|
end
|
23
26
|
|
24
|
-
if torun.include?
|
27
|
+
if torun.include? :api
|
25
28
|
api = Thread.new do
|
26
|
-
thr = Vmpooler::API.new
|
27
29
|
redis = Vmpooler.new_redis(redis_host, redis_port, redis_password)
|
28
|
-
|
29
|
-
thr.helpers.execute!
|
30
|
+
Vmpooler::API.execute(torun, config, redis, metrics, logger)
|
30
31
|
end
|
31
32
|
torun_threads << api
|
33
|
+
elsif metrics.respond_to?(:setup_prometheus_metrics)
|
34
|
+
# Run the cut down API - Prometheus Metrics only.
|
35
|
+
prometheus_only_api = Thread.new do
|
36
|
+
Vmpooler::API.execute(torun, config, nil, metrics, logger)
|
37
|
+
end
|
38
|
+
torun_threads << prometheus_only_api
|
32
39
|
end
|
33
40
|
|
34
|
-
if torun.include?
|
41
|
+
if torun.include? :manager
|
35
42
|
manager = Thread.new do
|
36
43
|
Vmpooler::PoolManager.new(
|
37
44
|
config,
|
38
|
-
|
39
|
-
Vmpooler.
|
45
|
+
logger,
|
46
|
+
Vmpooler.redis_connection_pool(redis_host, redis_port, redis_password, redis_connection_pool_size, redis_connection_pool_timeout, metrics),
|
40
47
|
metrics
|
41
48
|
).execute!
|
42
49
|
end
|
data/lib/vmpooler.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Vmpooler
|
4
|
+
require 'concurrent'
|
4
5
|
require 'date'
|
5
6
|
require 'json'
|
6
7
|
require 'net/ldap'
|
@@ -14,7 +15,7 @@ module Vmpooler
|
|
14
15
|
require 'timeout'
|
15
16
|
require 'yaml'
|
16
17
|
|
17
|
-
%w[api
|
18
|
+
%w[api metrics logger pool_manager generic_connection_pool].each do |lib|
|
18
19
|
require "vmpooler/#{lib}"
|
19
20
|
end
|
20
21
|
|
@@ -58,9 +59,14 @@ module Vmpooler
|
|
58
59
|
|
59
60
|
# Set some configuration defaults
|
60
61
|
parsed_config[:config]['task_limit'] = string_to_int(ENV['TASK_LIMIT']) || parsed_config[:config]['task_limit'] || 10
|
62
|
+
parsed_config[:config]['ondemand_clone_limit'] = string_to_int(ENV['ONDEMAND_CLONE_LIMIT']) || parsed_config[:config]['ondemand_clone_limit'] || 10
|
63
|
+
parsed_config[:config]['max_ondemand_instances_per_request'] = string_to_int(ENV['MAX_ONDEMAND_INSTANCES_PER_REQUEST']) || parsed_config[:config]['max_ondemand_instances_per_request'] || 10
|
61
64
|
parsed_config[:config]['migration_limit'] = string_to_int(ENV['MIGRATION_LIMIT']) if ENV['MIGRATION_LIMIT']
|
62
65
|
parsed_config[:config]['vm_checktime'] = string_to_int(ENV['VM_CHECKTIME']) || parsed_config[:config]['vm_checktime'] || 1
|
63
66
|
parsed_config[:config]['vm_lifetime'] = string_to_int(ENV['VM_LIFETIME']) || parsed_config[:config]['vm_lifetime'] || 24
|
67
|
+
parsed_config[:config]['max_lifetime_upper_limit'] = string_to_int(ENV['MAX_LIFETIME_UPPER_LIMIT']) || parsed_config[:config]['max_lifetime_upper_limit']
|
68
|
+
parsed_config[:config]['ready_ttl'] = string_to_int(ENV['READY_TTL']) || parsed_config[:config]['ready_ttl'] || 60
|
69
|
+
parsed_config[:config]['ondemand_request_ttl'] = string_to_int(ENV['ONDEMAND_REQUEST_TTL']) || parsed_config[:config]['ondemand_request_ttl'] || 5
|
64
70
|
parsed_config[:config]['prefix'] = ENV['PREFIX'] || parsed_config[:config]['prefix'] || ''
|
65
71
|
|
66
72
|
parsed_config[:config]['logfile'] = ENV['LOGFILE'] if ENV['LOGFILE']
|
@@ -72,18 +78,21 @@ module Vmpooler
|
|
72
78
|
parsed_config[:config]['vm_lifetime_auth'] = string_to_int(ENV['VM_LIFETIME_AUTH']) if ENV['VM_LIFETIME_AUTH']
|
73
79
|
parsed_config[:config]['max_tries'] = string_to_int(ENV['MAX_TRIES']) if ENV['MAX_TRIES']
|
74
80
|
parsed_config[:config]['retry_factor'] = string_to_int(ENV['RETRY_FACTOR']) if ENV['RETRY_FACTOR']
|
75
|
-
parsed_config[:config]['create_folders'] = ENV['CREATE_FOLDERS'] if ENV['CREATE_FOLDERS']
|
81
|
+
parsed_config[:config]['create_folders'] = true?(ENV['CREATE_FOLDERS']) if ENV['CREATE_FOLDERS']
|
76
82
|
parsed_config[:config]['create_template_delta_disks'] = ENV['CREATE_TEMPLATE_DELTA_DISKS'] if ENV['CREATE_TEMPLATE_DELTA_DISKS']
|
77
83
|
set_linked_clone(parsed_config)
|
78
84
|
parsed_config[:config]['experimental_features'] = ENV['EXPERIMENTAL_FEATURES'] if ENV['EXPERIMENTAL_FEATURES']
|
79
85
|
parsed_config[:config]['purge_unconfigured_folders'] = ENV['PURGE_UNCONFIGURED_FOLDERS'] if ENV['PURGE_UNCONFIGURED_FOLDERS']
|
80
86
|
parsed_config[:config]['usage_stats'] = ENV['USAGE_STATS'] if ENV['USAGE_STATS']
|
87
|
+
parsed_config[:config]['request_logger'] = ENV['REQUEST_LOGGER'] if ENV['REQUEST_LOGGER']
|
81
88
|
|
82
89
|
parsed_config[:redis] = parsed_config[:redis] || {}
|
83
90
|
parsed_config[:redis]['server'] = ENV['REDIS_SERVER'] || parsed_config[:redis]['server'] || 'localhost'
|
84
91
|
parsed_config[:redis]['port'] = string_to_int(ENV['REDIS_PORT']) if ENV['REDIS_PORT']
|
85
92
|
parsed_config[:redis]['password'] = ENV['REDIS_PASSWORD'] if ENV['REDIS_PASSWORD']
|
86
93
|
parsed_config[:redis]['data_ttl'] = string_to_int(ENV['REDIS_DATA_TTL']) || parsed_config[:redis]['data_ttl'] || 168
|
94
|
+
parsed_config[:redis]['connection_pool_size'] = string_to_int(ENV['REDIS_CONNECTION_POOL_SIZE']) || parsed_config[:redis]['connection_pool_size'] || 10
|
95
|
+
parsed_config[:redis]['connection_pool_timeout'] = string_to_int(ENV['REDIS_CONNECTION_POOL_TIMEOUT']) || parsed_config[:redis]['connection_pool_timeout'] || 5
|
87
96
|
|
88
97
|
parsed_config[:statsd] = parsed_config[:statsd] || {} if ENV['STATSD_SERVER']
|
89
98
|
parsed_config[:statsd]['server'] = ENV['STATSD_SERVER'] if ENV['STATSD_SERVER']
|
@@ -117,6 +126,7 @@ module Vmpooler
|
|
117
126
|
|
118
127
|
parsed_config[:pools].each do |pool|
|
119
128
|
parsed_config[:pool_names] << pool['name']
|
129
|
+
pool['ready_ttl'] ||= parsed_config[:config]['ready_ttl']
|
120
130
|
if pool['alias']
|
121
131
|
if pool['alias'].is_a?(Array)
|
122
132
|
pool['alias'].each do |pool_alias|
|
@@ -154,22 +164,22 @@ module Vmpooler
|
|
154
164
|
pools
|
155
165
|
end
|
156
166
|
|
157
|
-
def self.
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
167
|
+
def self.redis_connection_pool(host, port, password, size, timeout, metrics)
|
168
|
+
Vmpooler::PoolManager::GenericConnectionPool.new(
|
169
|
+
metrics: metrics,
|
170
|
+
connpool_type: 'redis_connection_pool',
|
171
|
+
connpool_provider: 'manager',
|
172
|
+
size: size,
|
173
|
+
timeout: timeout
|
174
|
+
) do
|
175
|
+
connection = Concurrent::Hash.new
|
176
|
+
redis = new_redis(host, port, password)
|
177
|
+
connection['connection'] = redis
|
178
|
+
end
|
163
179
|
end
|
164
180
|
|
165
|
-
def self.
|
166
|
-
|
167
|
-
Vmpooler::Statsd.new(params[:statsd])
|
168
|
-
elsif params[:graphite]
|
169
|
-
Vmpooler::Graphite.new(params[:graphite])
|
170
|
-
else
|
171
|
-
Vmpooler::DummyStatsd.new
|
172
|
-
end
|
181
|
+
def self.new_redis(host = 'localhost', port = nil, password = nil)
|
182
|
+
Redis.new(host: host, port: port, password: password)
|
173
183
|
end
|
174
184
|
|
175
185
|
def self.pools(conf)
|
data/lib/vmpooler/api.rb
CHANGED
@@ -2,51 +2,58 @@
|
|
2
2
|
|
3
3
|
module Vmpooler
|
4
4
|
class API < Sinatra::Base
|
5
|
-
|
6
|
-
|
5
|
+
# Load API components
|
6
|
+
%w[helpers dashboard reroute v1 request_logger].each do |lib|
|
7
|
+
require "vmpooler/api/#{lib}"
|
7
8
|
end
|
9
|
+
# Load dashboard components
|
10
|
+
require 'vmpooler/dashboard'
|
8
11
|
|
9
|
-
|
10
|
-
|
12
|
+
def self.execute(torun, config, redis, metrics, logger)
|
13
|
+
self.settings.set :config, config
|
14
|
+
self.settings.set :redis, redis unless redis.nil?
|
15
|
+
self.settings.set :metrics, metrics
|
16
|
+
self.settings.set :checkoutlock, Mutex.new
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
18
|
+
# Deflating in all situations
|
19
|
+
# https://www.schneems.com/2017/11/08/80-smaller-rails-footprint-with-rack-deflate/
|
20
|
+
use Rack::Deflater
|
15
21
|
|
16
|
-
|
17
|
-
|
22
|
+
# not_found clause placed here to fix rspec test issue.
|
23
|
+
not_found do
|
24
|
+
content_type :json
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
rescue LoadError
|
23
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'dashboard'))
|
24
|
-
end
|
26
|
+
result = {
|
27
|
+
ok: false
|
28
|
+
}
|
25
29
|
|
26
|
-
|
30
|
+
JSON.pretty_generate(result)
|
31
|
+
end
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
if metrics.respond_to?(:setup_prometheus_metrics)
|
34
|
+
# Prometheus metrics are only setup if actually specified
|
35
|
+
# in the config file.
|
36
|
+
metrics.setup_prometheus_metrics(torun)
|
37
|
+
|
38
|
+
# Using customised collector that filters out hostnames on API paths
|
39
|
+
require 'vmpooler/metrics/promstats/collector_middleware'
|
40
|
+
require 'prometheus/middleware/exporter'
|
41
|
+
use Vmpooler::Metrics::Promstats::CollectorMiddleware, metrics_prefix: "#{metrics.metrics_prefix}_http"
|
42
|
+
use Prometheus::Middleware::Exporter, path: metrics.endpoint
|
34
43
|
end
|
35
|
-
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
if torun.include? :api
|
46
|
+
# Enable API request logging only if required
|
47
|
+
use Vmpooler::API::RequestLogger, logger: logger if config[:config]['request_logger']
|
40
48
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
49
|
+
use Vmpooler::Dashboard
|
50
|
+
use Vmpooler::API::Dashboard
|
51
|
+
use Vmpooler::API::Reroute
|
52
|
+
use Vmpooler::API::V1
|
53
|
+
end
|
47
54
|
|
48
|
-
|
49
|
-
self.
|
55
|
+
# Get thee started O WebServer
|
56
|
+
self.run!
|
50
57
|
end
|
51
58
|
end
|
52
59
|
end
|
data/lib/vmpooler/api/helpers.rb
CHANGED
@@ -13,7 +13,7 @@ module Vmpooler
|
|
13
13
|
def valid_token?(backend)
|
14
14
|
return false unless has_token?
|
15
15
|
|
16
|
-
backend.exists('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN']) ? true : false
|
16
|
+
backend.exists?('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN']) ? true : false
|
17
17
|
end
|
18
18
|
|
19
19
|
def validate_token(backend)
|
@@ -238,7 +238,7 @@ module Vmpooler
|
|
238
238
|
queue[:running] = get_total_across_pools_redis_scard(pools, 'vmpooler__running__', backend)
|
239
239
|
queue[:completed] = get_total_across_pools_redis_scard(pools, 'vmpooler__completed__', backend)
|
240
240
|
|
241
|
-
queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i
|
241
|
+
queue[:cloning] = backend.get('vmpooler__tasks__clone').to_i + backend.get('vmpooler__tasks__ondemandclone').to_i
|
242
242
|
queue[:booting] = queue[:pending].to_i - queue[:cloning].to_i
|
243
243
|
queue[:booting] = 0 if queue[:booting] < 0
|
244
244
|
queue[:total] = queue[:pending].to_i + queue[:ready].to_i + queue[:running].to_i + queue[:completed].to_i
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vmpooler
|
4
|
+
class API
|
5
|
+
class RequestLogger
|
6
|
+
attr_reader :app
|
7
|
+
|
8
|
+
def initialize(app, options = {})
|
9
|
+
@app = app
|
10
|
+
@logger = options[:logger]
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
status, headers, body = @app.call(env)
|
15
|
+
@logger.log('s', "[ ] API: Method: #{env['REQUEST_METHOD']}, Status: #{status}, Path: #{env['PATH_INFO']}, Body: #{body}")
|
16
|
+
[status, headers, body]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/vmpooler/api/v1.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'vmpooler/util/parsing'
|
4
|
+
|
3
5
|
module Vmpooler
|
4
6
|
class API
|
5
7
|
class V1 < Sinatra::Base
|
@@ -42,6 +44,68 @@ module Vmpooler
|
|
42
44
|
Vmpooler::API.settings.checkoutlock
|
43
45
|
end
|
44
46
|
|
47
|
+
def get_template_aliases(template)
|
48
|
+
result = []
|
49
|
+
aliases = Vmpooler::API.settings.config[:alias]
|
50
|
+
if aliases
|
51
|
+
result += aliases[template] if aliases[template].is_a?(Array)
|
52
|
+
template_backends << aliases[template] if aliases[template].is_a?(String)
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_pool_weights(template_backends)
|
58
|
+
pool_index = pool_index(pools)
|
59
|
+
weighted_pools = {}
|
60
|
+
template_backends.each do |t|
|
61
|
+
next unless pool_index.key? t
|
62
|
+
|
63
|
+
index = pool_index[t]
|
64
|
+
clone_target = pools[index]['clone_target'] || config['clone_target']
|
65
|
+
next unless config.key?('backend_weight')
|
66
|
+
|
67
|
+
weight = config['backend_weight'][clone_target]
|
68
|
+
if weight
|
69
|
+
weighted_pools[t] = weight
|
70
|
+
end
|
71
|
+
end
|
72
|
+
weighted_pools
|
73
|
+
end
|
74
|
+
|
75
|
+
def count_selection(selection)
|
76
|
+
result = {}
|
77
|
+
selection.uniq.each do |poolname|
|
78
|
+
result[poolname] = selection.count(poolname)
|
79
|
+
end
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
def evaluate_template_aliases(template, count)
|
84
|
+
template_backends = []
|
85
|
+
template_backends << template if backend.sismember('vmpooler__pools', template)
|
86
|
+
selection = []
|
87
|
+
aliases = get_template_aliases(template)
|
88
|
+
if aliases
|
89
|
+
template_backends += aliases
|
90
|
+
weighted_pools = get_pool_weights(template_backends)
|
91
|
+
|
92
|
+
pickup = Pickup.new(weighted_pools) if weighted_pools.count == template_backends.count
|
93
|
+
count.to_i.times do
|
94
|
+
if pickup
|
95
|
+
selection << pickup.pick
|
96
|
+
else
|
97
|
+
selection << template_backends.sample
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
count.to_i.times do
|
102
|
+
selection << template
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
count_selection(selection)
|
107
|
+
end
|
108
|
+
|
45
109
|
def fetch_single_vm(template)
|
46
110
|
template_backends = [template]
|
47
111
|
aliases = Vmpooler::API.settings.config[:alias]
|
@@ -245,11 +309,9 @@ module Vmpooler
|
|
245
309
|
pool_index = pool_index(pools)
|
246
310
|
template_configs = backend.hgetall('vmpooler__config__template')
|
247
311
|
template_configs&.each do |poolname, template|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
252
|
-
end
|
312
|
+
next unless pool_index.include? poolname
|
313
|
+
|
314
|
+
pools[pool_index[poolname]]['template'] = template
|
253
315
|
end
|
254
316
|
end
|
255
317
|
|
@@ -257,11 +319,9 @@ module Vmpooler
|
|
257
319
|
pool_index = pool_index(pools)
|
258
320
|
poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
|
259
321
|
poolsize_configs&.each do |poolname, size|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
end
|
264
|
-
end
|
322
|
+
next unless pool_index.include? poolname
|
323
|
+
|
324
|
+
pools[pool_index[poolname]]['size'] = size.to_i
|
265
325
|
end
|
266
326
|
end
|
267
327
|
|
@@ -269,12 +329,70 @@ module Vmpooler
|
|
269
329
|
pool_index = pool_index(pools)
|
270
330
|
clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
|
271
331
|
clone_target_configs&.each do |poolname, clone_target|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
332
|
+
next unless pool_index.include? poolname
|
333
|
+
|
334
|
+
pools[pool_index[poolname]]['clone_target'] = clone_target
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def too_many_requested?(payload)
|
339
|
+
payload&.each do |poolname, count|
|
340
|
+
next unless count.to_i > config['max_ondemand_instances_per_request']
|
341
|
+
|
342
|
+
metrics.increment('ondemandrequest_fail.toomanyrequests.' + poolname)
|
343
|
+
return true
|
344
|
+
end
|
345
|
+
false
|
346
|
+
end
|
347
|
+
|
348
|
+
def generate_ondemand_request(payload)
|
349
|
+
result = { 'ok': false }
|
350
|
+
|
351
|
+
requested_instances = payload.reject { |k, _v| k == 'request_id' }
|
352
|
+
if too_many_requested?(requested_instances)
|
353
|
+
result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
|
354
|
+
status 403
|
355
|
+
return result
|
356
|
+
end
|
357
|
+
|
358
|
+
score = Time.now.to_i
|
359
|
+
request_id = payload['request_id']
|
360
|
+
request_id ||= generate_request_id
|
361
|
+
result['request_id'] = request_id
|
362
|
+
|
363
|
+
if backend.exists?("vmpooler__odrequest__#{request_id}")
|
364
|
+
result['message'] = "request_id '#{request_id}' has already been created"
|
365
|
+
status 409
|
366
|
+
metrics.increment('ondemandrequest_generate.duplicaterequests')
|
367
|
+
return result
|
368
|
+
end
|
369
|
+
|
370
|
+
status 201
|
371
|
+
|
372
|
+
platforms_with_aliases = []
|
373
|
+
requested_instances.each do |poolname, count|
|
374
|
+
selection = evaluate_template_aliases(poolname, count)
|
375
|
+
selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
|
376
|
+
end
|
377
|
+
platforms_string = platforms_with_aliases.join(',')
|
378
|
+
|
379
|
+
return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
|
380
|
+
|
381
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
|
382
|
+
if Vmpooler::API.settings.config[:auth] and has_token?
|
383
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
384
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
|
385
|
+
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user'))
|
277
386
|
end
|
387
|
+
|
388
|
+
result['domain'] = config['domain'] if config['domain']
|
389
|
+
result[:ok] = true
|
390
|
+
metrics.increment('ondemandrequest_generate.success')
|
391
|
+
result
|
392
|
+
end
|
393
|
+
|
394
|
+
def generate_request_id
|
395
|
+
SecureRandom.uuid
|
278
396
|
end
|
279
397
|
|
280
398
|
get '/' do
|
@@ -395,7 +513,7 @@ module Vmpooler
|
|
395
513
|
end
|
396
514
|
|
397
515
|
# for backwards compatibility, include separate "empty" stats in "status" block
|
398
|
-
if ready == 0
|
516
|
+
if ready == 0 && max != 0
|
399
517
|
result[:status][:empty] ||= []
|
400
518
|
result[:status][:empty].push(pool['name'])
|
401
519
|
|
@@ -689,9 +807,96 @@ module Vmpooler
|
|
689
807
|
JSON.pretty_generate(result)
|
690
808
|
end
|
691
809
|
|
810
|
+
post "#{api_prefix}/ondemandvm/?" do
|
811
|
+
content_type :json
|
812
|
+
metrics.increment('api_vm.post.ondemand.requestid')
|
813
|
+
|
814
|
+
need_token! if Vmpooler::API.settings.config[:auth]
|
815
|
+
|
816
|
+
result = { 'ok' => false }
|
817
|
+
|
818
|
+
begin
|
819
|
+
payload = JSON.parse(request.body.read)
|
820
|
+
|
821
|
+
if payload
|
822
|
+
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
823
|
+
if invalid.empty?
|
824
|
+
result = generate_ondemand_request(payload)
|
825
|
+
else
|
826
|
+
result[:bad_templates] = invalid
|
827
|
+
invalid.each do |bad_template|
|
828
|
+
metrics.increment('ondemandrequest_fail.invalid.' + bad_template)
|
829
|
+
end
|
830
|
+
status 404
|
831
|
+
end
|
832
|
+
else
|
833
|
+
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
834
|
+
status 404
|
835
|
+
end
|
836
|
+
rescue JSON::ParserError
|
837
|
+
status 400
|
838
|
+
result = {
|
839
|
+
'ok' => false,
|
840
|
+
'message' => 'JSON payload could not be parsed'
|
841
|
+
}
|
842
|
+
end
|
843
|
+
|
844
|
+
JSON.pretty_generate(result)
|
845
|
+
end
|
846
|
+
|
847
|
+
post "#{api_prefix}/ondemandvm/:template/?" do
|
848
|
+
content_type :json
|
849
|
+
result = { 'ok' => false }
|
850
|
+
metrics.increment('api_vm.delete.ondemand.template')
|
851
|
+
|
852
|
+
need_token! if Vmpooler::API.settings.config[:auth]
|
853
|
+
|
854
|
+
payload = extract_templates_from_query_params(params[:template])
|
855
|
+
|
856
|
+
if payload
|
857
|
+
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
858
|
+
if invalid.empty?
|
859
|
+
result = generate_ondemand_request(payload)
|
860
|
+
else
|
861
|
+
result[:bad_templates] = invalid
|
862
|
+
invalid.each do |bad_template|
|
863
|
+
metrics.increment('ondemandrequest_fail.invalid.' + bad_template)
|
864
|
+
end
|
865
|
+
status 404
|
866
|
+
end
|
867
|
+
else
|
868
|
+
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
869
|
+
status 404
|
870
|
+
end
|
871
|
+
|
872
|
+
JSON.pretty_generate(result)
|
873
|
+
end
|
874
|
+
|
875
|
+
get "#{api_prefix}/ondemandvm/:requestid/?" do
|
876
|
+
content_type :json
|
877
|
+
metrics.increment('api_vm.get.ondemand.request')
|
878
|
+
|
879
|
+
status 404
|
880
|
+
result = check_ondemand_request(params[:requestid])
|
881
|
+
|
882
|
+
JSON.pretty_generate(result)
|
883
|
+
end
|
884
|
+
|
885
|
+
delete "#{api_prefix}/ondemandvm/:requestid/?" do
|
886
|
+
content_type :json
|
887
|
+
need_token! if Vmpooler::API.settings.config[:auth]
|
888
|
+
metrics.increment('api_vm.delete.ondemand.request')
|
889
|
+
|
890
|
+
status 404
|
891
|
+
result = delete_ondemand_request(params[:requestid])
|
892
|
+
|
893
|
+
JSON.pretty_generate(result)
|
894
|
+
end
|
895
|
+
|
692
896
|
post "#{api_prefix}/vm/?" do
|
693
897
|
content_type :json
|
694
898
|
result = { 'ok' => false }
|
899
|
+
metrics.increment('api_vm.post.vm.checkout')
|
695
900
|
|
696
901
|
payload = JSON.parse(request.body.read)
|
697
902
|
|
@@ -764,9 +969,77 @@ module Vmpooler
|
|
764
969
|
invalid
|
765
970
|
end
|
766
971
|
|
972
|
+
def check_ondemand_request(request_id)
|
973
|
+
result = { 'ok' => false }
|
974
|
+
request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
|
975
|
+
if request_hash.empty?
|
976
|
+
result['message'] = "no request found for request_id '#{request_id}'"
|
977
|
+
return result
|
978
|
+
end
|
979
|
+
|
980
|
+
result['request_id'] = request_id
|
981
|
+
result['ready'] = false
|
982
|
+
result['ok'] = true
|
983
|
+
status 202
|
984
|
+
|
985
|
+
if request_hash['status'] == 'ready'
|
986
|
+
result['ready'] = true
|
987
|
+
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
|
988
|
+
instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
989
|
+
result[platform_alias] = { 'hostname': instances }
|
990
|
+
end
|
991
|
+
result['domain'] = config['domain'] if config['domain']
|
992
|
+
status 200
|
993
|
+
elsif request_hash['status'] == 'failed'
|
994
|
+
result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
|
995
|
+
status 200
|
996
|
+
elsif request_hash['status'] == 'deleted'
|
997
|
+
result['message'] = 'The request has been deleted'
|
998
|
+
status 200
|
999
|
+
else
|
1000
|
+
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
|
1001
|
+
instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
1002
|
+
result[platform_alias] = {
|
1003
|
+
'ready': instance_count.to_s,
|
1004
|
+
'pending': (count.to_i - instance_count.to_i).to_s
|
1005
|
+
}
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
result
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
def delete_ondemand_request(request_id)
|
1013
|
+
result = { 'ok' => false }
|
1014
|
+
|
1015
|
+
platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
|
1016
|
+
unless platforms
|
1017
|
+
result['message'] = "no request found for request_id '#{request_id}'"
|
1018
|
+
return result
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
|
1022
|
+
result['message'] = 'the request has already been deleted'
|
1023
|
+
else
|
1024
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
|
1025
|
+
|
1026
|
+
Parsing.get_platform_pool_count(platforms) do |platform_alias, pool, _count|
|
1027
|
+
backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")&.each do |vm|
|
1028
|
+
backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
|
1029
|
+
end
|
1030
|
+
backend.del("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
1031
|
+
end
|
1032
|
+
backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
|
1033
|
+
end
|
1034
|
+
status 200
|
1035
|
+
result['ok'] = true
|
1036
|
+
result
|
1037
|
+
end
|
1038
|
+
|
767
1039
|
post "#{api_prefix}/vm/:template/?" do
|
768
1040
|
content_type :json
|
769
1041
|
result = { 'ok' => false }
|
1042
|
+
metrics.increment('api_vm.get.vm.template')
|
770
1043
|
|
771
1044
|
payload = extract_templates_from_query_params(params[:template])
|
772
1045
|
|
@@ -790,6 +1063,7 @@ module Vmpooler
|
|
790
1063
|
|
791
1064
|
get "#{api_prefix}/vm/:hostname/?" do
|
792
1065
|
content_type :json
|
1066
|
+
metrics.increment('api_vm.get.vm.hostname')
|
793
1067
|
|
794
1068
|
result = {}
|
795
1069
|
|
@@ -862,6 +1136,7 @@ module Vmpooler
|
|
862
1136
|
|
863
1137
|
delete "#{api_prefix}/vm/:hostname/?" do
|
864
1138
|
content_type :json
|
1139
|
+
metrics.increment('api_vm.delete.vm.hostname')
|
865
1140
|
|
866
1141
|
result = {}
|
867
1142
|
|
@@ -879,8 +1154,9 @@ module Vmpooler
|
|
879
1154
|
|
880
1155
|
status 200
|
881
1156
|
result['ok'] = true
|
1157
|
+
metrics.increment('delete.success')
|
882
1158
|
else
|
883
|
-
metrics.increment('delete.
|
1159
|
+
metrics.increment('delete.failed')
|
884
1160
|
end
|
885
1161
|
end
|
886
1162
|
|
@@ -889,6 +1165,7 @@ module Vmpooler
|
|
889
1165
|
|
890
1166
|
put "#{api_prefix}/vm/:hostname/?" do
|
891
1167
|
content_type :json
|
1168
|
+
metrics.increment('api_vm.put.vm.modify')
|
892
1169
|
|
893
1170
|
status 404
|
894
1171
|
result = { 'ok' => false }
|
@@ -897,7 +1174,7 @@ module Vmpooler
|
|
897
1174
|
|
898
1175
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
899
1176
|
|
900
|
-
if backend.exists('vmpooler__vm__' + params[:hostname])
|
1177
|
+
if backend.exists?('vmpooler__vm__' + params[:hostname])
|
901
1178
|
begin
|
902
1179
|
jdata = JSON.parse(request.body.read)
|
903
1180
|
rescue StandardError
|
@@ -923,6 +1200,7 @@ module Vmpooler
|
|
923
1200
|
unless arg.to_i > 0
|
924
1201
|
failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.")
|
925
1202
|
end
|
1203
|
+
|
926
1204
|
when 'tags'
|
927
1205
|
unless arg.is_a?(Hash)
|
928
1206
|
failure.push("You provided tags (#{arg}) as something other than a hash.")
|
@@ -964,6 +1242,7 @@ module Vmpooler
|
|
964
1242
|
|
965
1243
|
post "#{api_prefix}/vm/:hostname/disk/:size/?" do
|
966
1244
|
content_type :json
|
1245
|
+
metrics.increment('api_vm.post.vm.disksize')
|
967
1246
|
|
968
1247
|
need_token! if Vmpooler::API.settings.config[:auth]
|
969
1248
|
|
@@ -972,7 +1251,7 @@ module Vmpooler
|
|
972
1251
|
|
973
1252
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
974
1253
|
|
975
|
-
if ((params[:size].to_i > 0 )and (backend.exists('vmpooler__vm__' + params[:hostname])))
|
1254
|
+
if ((params[:size].to_i > 0 )and (backend.exists?('vmpooler__vm__' + params[:hostname])))
|
976
1255
|
result[params[:hostname]] = {}
|
977
1256
|
result[params[:hostname]]['disk'] = "+#{params[:size]}gb"
|
978
1257
|
|
@@ -987,6 +1266,7 @@ module Vmpooler
|
|
987
1266
|
|
988
1267
|
post "#{api_prefix}/vm/:hostname/snapshot/?" do
|
989
1268
|
content_type :json
|
1269
|
+
metrics.increment('api_vm.post.vm.snapshot')
|
990
1270
|
|
991
1271
|
need_token! if Vmpooler::API.settings.config[:auth]
|
992
1272
|
|
@@ -995,7 +1275,7 @@ module Vmpooler
|
|
995
1275
|
|
996
1276
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
997
1277
|
|
998
|
-
if backend.exists('vmpooler__vm__' + params[:hostname])
|
1278
|
+
if backend.exists?('vmpooler__vm__' + params[:hostname])
|
999
1279
|
result[params[:hostname]] = {}
|
1000
1280
|
|
1001
1281
|
o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten
|
@@ -1012,6 +1292,7 @@ module Vmpooler
|
|
1012
1292
|
|
1013
1293
|
post "#{api_prefix}/vm/:hostname/snapshot/:snapshot/?" do
|
1014
1294
|
content_type :json
|
1295
|
+
metrics.increment('api_vm.post.vm.disksize')
|
1015
1296
|
|
1016
1297
|
need_token! if Vmpooler::API.settings.config[:auth]
|
1017
1298
|
|
@@ -1047,7 +1328,7 @@ module Vmpooler
|
|
1047
1328
|
invalid.each do |bad_template|
|
1048
1329
|
metrics.increment("config.invalid.#{bad_template}")
|
1049
1330
|
end
|
1050
|
-
result[:
|
1331
|
+
result[:not_configured] = invalid
|
1051
1332
|
status 400
|
1052
1333
|
end
|
1053
1334
|
else
|