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 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