vmpooler 0.12.0 → 0.14.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/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
|