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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19dd8ce83614c49e92f13e043c2ee7865f1c664d92aa8f13e47fc328eb033690
4
- data.tar.gz: c3618c9c13591b1b06748ac3e5d1c2dc7ba2eb9793ea2f3f014a762fd6deb4ed
3
+ metadata.gz: 74df51cf1d525f0bc7afe2da930e68d0b2e1e014e10d184213556e4a13bd170b
4
+ data.tar.gz: 7cfe6d26ebb99e421f2b9daeabe1dfcb610cc8457cae1f79c5632df33eb6d426
5
5
  SHA512:
6
- metadata.gz: 8dcb8f8765f9c1fac52c2d0088aaa09a589b5145d5a81e901af614b8fcbbd588669b3f14e5c42f4d56d4681165f8814ef11b22073df6f97b85cef6fcb10d04e2
7
- data.tar.gz: d7c917233daee973486a6a8add55222a92bb1708521caaefdf2666824c0785cebd1ce7e298448a66736bf7e0646f02a560067e54cc1d06a07ca6abae645898c9
6
+ metadata.gz: d4e00e1cf43c08652b6186a741f048a487c1300744a2a40912759bdf06a2ad306fd59ae9adb10364be191047edc4cc84d8ea99fbf211806d59228b0993e61d0f
7
+ data.tar.gz: 4948f3405e5969bcd55b350604d1a2b8bfa9270960b635c4448a9478764f79bef1b2af101e11a3389587cdc2bd0fb22f28c1b7d1582227414aa42764d8451a74
@@ -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
- metrics = Vmpooler.new_metrics(config)
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 = ['api', 'manager']
19
+ torun = %i[api manager]
17
20
  else
18
21
  torun = []
19
- torun << 'api' if ARGV.include? 'api'
20
- torun << 'manager' if ARGV.include? 'manager'
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? 'api'
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
- thr.helpers.configure(config, redis, metrics)
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? 'manager'
41
+ if torun.include? :manager
35
42
  manager = Thread.new do
36
43
  Vmpooler::PoolManager.new(
37
44
  config,
38
- Vmpooler.new_logger(logger_file),
39
- Vmpooler.new_redis(redis_host, redis_port, redis_password),
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
@@ -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 graphite logger pool_manager statsd dummy_statsd generic_connection_pool].each do |lib|
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.new_redis(host = 'localhost', port = nil, password = nil)
158
- Redis.new(host: host, port: port, password: password)
159
- end
160
-
161
- def self.new_logger(logfile)
162
- Vmpooler::Logger.new logfile
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.new_metrics(params)
166
- if params[:statsd]
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)
@@ -2,51 +2,58 @@
2
2
 
3
3
  module Vmpooler
4
4
  class API < Sinatra::Base
5
- def initialize
6
- super
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
- not_found do
10
- content_type :json
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
- result = {
13
- ok: false
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
- JSON.pretty_generate(result)
17
- end
22
+ # not_found clause placed here to fix rspec test issue.
23
+ not_found do
24
+ content_type :json
18
25
 
19
- # Load dashboard components
20
- begin
21
- require 'dashboard'
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
- use Vmpooler::Dashboard
30
+ JSON.pretty_generate(result)
31
+ end
27
32
 
28
- # Load API components
29
- %w[helpers dashboard reroute v1].each do |lib|
30
- begin
31
- require "api/#{lib}"
32
- rescue LoadError
33
- require File.expand_path(File.join(File.dirname(__FILE__), 'api', lib))
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
- use Vmpooler::API::Dashboard
38
- use Vmpooler::API::Reroute
39
- use Vmpooler::API::V1
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
- def configure(config, redis, metrics)
42
- self.settings.set :config, config
43
- self.settings.set :redis, redis
44
- self.settings.set :metrics, metrics
45
- self.settings.set :checkoutlock, Mutex.new
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
- def execute!
49
- self.settings.run!
55
+ # Get thee started O WebServer
56
+ self.run!
50
57
  end
51
58
  end
52
59
  end
@@ -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
@@ -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
- if pool_index.include? poolname
249
- unless pools[pool_index[poolname]]['template'] == template
250
- pools[pool_index[poolname]]['template'] = template
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
- if pool_index.include? poolname
261
- unless pools[pool_index[poolname]]['size'] == size.to_i
262
- pools[pool_index[poolname]]['size'] == size.to_i
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
- if pool_index.include? poolname
273
- unless pools[pool_index[poolname]]['clone_target'] == clone_target
274
- pools[pool_index[poolname]]['clone_target'] == clone_target
275
- end
276
- end
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.srem.failed')
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[:bad_templates] = invalid
1331
+ result[:not_configured] = invalid
1051
1332
  status 400
1052
1333
  end
1053
1334
  else