vmpooler 0.11.2 → 0.13.2

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: b755def48f79be4614be09b1137b7ecb20a59feced1dc521fdc1ba9454a1efa2
4
- data.tar.gz: b3de3d1a6252b6bc3260235421366734f98c031e5895978d22060d770989e0d8
3
+ metadata.gz: a200f3215b389575c605363ed55bb21b9a1779084cee39fef88fe28c2a51e9ab
4
+ data.tar.gz: fd48ee0ff0fd5ea1f19dbd0b19bce49d821b0a17ef6f62aca91804cbdebc7d88
5
5
  SHA512:
6
- metadata.gz: ba6ef4065c811dffbdafc2035c40092f59fc9921a83595bcbe73a42f8b1df74d355a9bc573a9301e6245d70e61132a906feb3b0a22988b4bf11639e921e4d283
7
- data.tar.gz: 457c5212222bf9052cf9ef5e941417d75c85e86ef878910a44a3f35f8b4e4fa5ef22619bc0c10bea8be14091e2d79087b1292bee094719eddb60825e115dc5c6
6
+ metadata.gz: 63df277bab9ab5f049886d30166856ba5adfc85119e3408d91339827f6b05d3b12dde49c78c051c23d90bf123590e14d6cc7e41598cd315f2df9637a5c7c7042
7
+ data.tar.gz: 3f6ce744a9c792c12800d7da0b6738e995d8f9a0cbc86bd3635977b3c463765b371100f5ea3ad509a15b7bd83be5bf37e4ea72cf7004535a36afe263577ed356
@@ -7,6 +7,8 @@ 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
  metrics = Vmpooler.new_metrics(config)
@@ -36,7 +38,7 @@ if torun.include? 'manager'
36
38
  Vmpooler::PoolManager.new(
37
39
  config,
38
40
  Vmpooler.new_logger(logger_file),
39
- Vmpooler.new_redis(redis_host, redis_port, redis_password),
41
+ Vmpooler.redis_connection_pool(redis_host, redis_port, redis_password, redis_connection_pool_size, redis_connection_pool_timeout, metrics),
40
42
  metrics
41
43
  ).execute!
42
44
  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'
@@ -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,7 +78,7 @@ 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']
@@ -84,6 +90,8 @@ module Vmpooler
84
90
  parsed_config[:redis]['port'] = string_to_int(ENV['REDIS_PORT']) if ENV['REDIS_PORT']
85
91
  parsed_config[:redis]['password'] = ENV['REDIS_PASSWORD'] if ENV['REDIS_PASSWORD']
86
92
  parsed_config[:redis]['data_ttl'] = string_to_int(ENV['REDIS_DATA_TTL']) || parsed_config[:redis]['data_ttl'] || 168
93
+ parsed_config[:redis]['connection_pool_size'] = string_to_int(ENV['REDIS_CONNECTION_POOL_SIZE']) || parsed_config[:redis]['connection_pool_size'] || 10
94
+ parsed_config[:redis]['connection_pool_timeout'] = string_to_int(ENV['REDIS_CONNECTION_POOL_TIMEOUT']) || parsed_config[:redis]['connection_pool_timeout'] || 5
87
95
 
88
96
  parsed_config[:statsd] = parsed_config[:statsd] || {} if ENV['STATSD_SERVER']
89
97
  parsed_config[:statsd]['server'] = ENV['STATSD_SERVER'] if ENV['STATSD_SERVER']
@@ -117,6 +125,7 @@ module Vmpooler
117
125
 
118
126
  parsed_config[:pools].each do |pool|
119
127
  parsed_config[:pool_names] << pool['name']
128
+ pool['ready_ttl'] ||= parsed_config[:config]['ready_ttl']
120
129
  if pool['alias']
121
130
  if pool['alias'].is_a?(Array)
122
131
  pool['alias'].each do |pool_alias|
@@ -154,6 +163,19 @@ module Vmpooler
154
163
  pools
155
164
  end
156
165
 
166
+ def self.redis_connection_pool(host, port, password, size, timeout, metrics)
167
+ Vmpooler::PoolManager::GenericConnectionPool.new(
168
+ metrics: metrics,
169
+ metric_prefix: 'redis_connection_pool',
170
+ size: size,
171
+ timeout: timeout
172
+ ) do
173
+ connection = Concurrent::Hash.new
174
+ redis = new_redis(host, port, password)
175
+ connection['connection'] = redis
176
+ end
177
+ end
178
+
157
179
  def self.new_redis(host = 'localhost', port = nil, password = nil)
158
180
  Redis.new(host: host, port: port, password: password)
159
181
  end
@@ -84,29 +84,29 @@ module Vmpooler
84
84
  when 'ldap'
85
85
  ldap_base = auth[:ldap]['base']
86
86
  ldap_port = auth[:ldap]['port'] || 389
87
+ ldap_user_obj = auth[:ldap]['user_object']
88
+ ldap_host = auth[:ldap]['host']
87
89
 
88
- if ldap_base.is_a? Array
89
- ldap_base.each do |search_base|
90
+ unless ldap_base.is_a? Array
91
+ ldap_base = ldap_base.split
92
+ end
93
+
94
+ unless ldap_user_obj.is_a? Array
95
+ ldap_user_obj = ldap_user_obj.split
96
+ end
97
+
98
+ ldap_base.each do |search_base|
99
+ ldap_user_obj.each do |search_user_obj|
90
100
  result = authenticate_ldap(
91
101
  ldap_port,
92
- auth[:ldap]['host'],
93
- auth[:ldap]['user_object'],
102
+ ldap_host,
103
+ search_user_obj,
94
104
  search_base,
95
105
  username_str,
96
106
  password_str
97
107
  )
98
- return true if result == true
108
+ return true if result
99
109
  end
100
- else
101
- result = authenticate_ldap(
102
- ldap_port,
103
- auth[:ldap]['host'],
104
- auth[:ldap]['user_object'],
105
- ldap_base,
106
- username_str,
107
- password_str
108
- )
109
- return result
110
110
  end
111
111
 
112
112
  return false
@@ -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
@@ -42,6 +42,68 @@ module Vmpooler
42
42
  Vmpooler::API.settings.checkoutlock
43
43
  end
44
44
 
45
+ def get_template_aliases(template)
46
+ result = []
47
+ aliases = Vmpooler::API.settings.config[:alias]
48
+ if aliases
49
+ result += aliases[template] if aliases[template].is_a?(Array)
50
+ template_backends << aliases[template] if aliases[template].is_a?(String)
51
+ end
52
+ result
53
+ end
54
+
55
+ def get_pool_weights(template_backends)
56
+ pool_index = pool_index(pools)
57
+ weighted_pools = {}
58
+ template_backends.each do |t|
59
+ next unless pool_index.key? t
60
+
61
+ index = pool_index[t]
62
+ clone_target = pools[index]['clone_target'] || config['clone_target']
63
+ next unless config.key?('backend_weight')
64
+
65
+ weight = config['backend_weight'][clone_target]
66
+ if weight
67
+ weighted_pools[t] = weight
68
+ end
69
+ end
70
+ weighted_pools
71
+ end
72
+
73
+ def count_selection(selection)
74
+ result = {}
75
+ selection.uniq.each do |poolname|
76
+ result[poolname] = selection.count(poolname)
77
+ end
78
+ result
79
+ end
80
+
81
+ def evaluate_template_aliases(template, count)
82
+ template_backends = []
83
+ template_backends << template if backend.sismember('vmpooler__pools', template)
84
+ selection = []
85
+ aliases = get_template_aliases(template)
86
+ if aliases
87
+ template_backends += aliases
88
+ weighted_pools = get_pool_weights(template_backends)
89
+
90
+ pickup = Pickup.new(weighted_pools) if weighted_pools.count == template_backends.count
91
+ count.to_i.times do
92
+ if pickup
93
+ selection << pickup.pick
94
+ else
95
+ selection << template_backends.sample
96
+ end
97
+ end
98
+ else
99
+ count.to_i.times do
100
+ selection << template
101
+ end
102
+ end
103
+
104
+ count_selection(selection)
105
+ end
106
+
45
107
  def fetch_single_vm(template)
46
108
  template_backends = [template]
47
109
  aliases = Vmpooler::API.settings.config[:alias]
@@ -83,8 +145,13 @@ module Vmpooler
83
145
  vms.reverse.each do |vm|
84
146
  ready = vm_ready?(vm, config['domain'])
85
147
  if ready
86
- backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
87
- return [vm, template_backend, template]
148
+ smoved = backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
149
+ if smoved
150
+ return [vm, template_backend, template]
151
+ else
152
+ metrics.increment("checkout.smove.failed.#{template_backend}")
153
+ return [nil, nil, nil]
154
+ end
88
155
  else
89
156
  backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__completed__#{template_backend}", vm)
90
157
  metrics.increment("checkout.nonresponsive.#{template_backend}")
@@ -240,11 +307,9 @@ module Vmpooler
240
307
  pool_index = pool_index(pools)
241
308
  template_configs = backend.hgetall('vmpooler__config__template')
242
309
  template_configs&.each do |poolname, template|
243
- if pool_index.include? poolname
244
- unless pools[pool_index[poolname]]['template'] == template
245
- pools[pool_index[poolname]]['template'] = template
246
- end
247
- end
310
+ next unless pool_index.include? poolname
311
+
312
+ pools[pool_index[poolname]]['template'] = template
248
313
  end
249
314
  end
250
315
 
@@ -252,11 +317,9 @@ module Vmpooler
252
317
  pool_index = pool_index(pools)
253
318
  poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
254
319
  poolsize_configs&.each do |poolname, size|
255
- if pool_index.include? poolname
256
- unless pools[pool_index[poolname]]['size'] == size.to_i
257
- pools[pool_index[poolname]]['size'] == size.to_i
258
- end
259
- end
320
+ next unless pool_index.include? poolname
321
+
322
+ pools[pool_index[poolname]]['size'] = size.to_i
260
323
  end
261
324
  end
262
325
 
@@ -264,12 +327,67 @@ module Vmpooler
264
327
  pool_index = pool_index(pools)
265
328
  clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
266
329
  clone_target_configs&.each do |poolname, clone_target|
267
- if pool_index.include? poolname
268
- unless pools[pool_index[poolname]]['clone_target'] == clone_target
269
- pools[pool_index[poolname]]['clone_target'] == clone_target
270
- end
271
- end
330
+ next unless pool_index.include? poolname
331
+
332
+ pools[pool_index[poolname]]['clone_target'] = clone_target
333
+ end
334
+ end
335
+
336
+ def too_many_requested?(payload)
337
+ payload&.each do |_poolname, count|
338
+ next unless count.to_i > config['max_ondemand_instances_per_request']
339
+
340
+ return true
341
+ end
342
+ false
343
+ end
344
+
345
+ def generate_ondemand_request(payload)
346
+ result = { 'ok': false }
347
+
348
+ requested_instances = payload.reject { |k, _v| k == 'request_id' }
349
+ if too_many_requested?(requested_instances)
350
+ result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
351
+ status 403
352
+ return result
353
+ end
354
+
355
+ score = Time.now.to_i
356
+ request_id = payload['request_id']
357
+ request_id ||= generate_request_id
358
+ result['request_id'] = request_id
359
+
360
+ if backend.exists("vmpooler__odrequest__#{request_id}")
361
+ result['message'] = "request_id '#{request_id}' has already been created"
362
+ status 409
363
+ return result
364
+ end
365
+
366
+ status 201
367
+
368
+ platforms_with_aliases = []
369
+ requested_instances.each do |poolname, count|
370
+ selection = evaluate_template_aliases(poolname, count)
371
+ selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
372
+ end
373
+ platforms_string = platforms_with_aliases.join(',')
374
+
375
+ return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
376
+
377
+ backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
378
+ if Vmpooler::API.settings.config[:auth] and has_token?
379
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
380
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
381
+ backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user'))
272
382
  end
383
+
384
+ result['domain'] = config['domain'] if config['domain']
385
+ result[:ok] = true
386
+ result
387
+ end
388
+
389
+ def generate_request_id
390
+ SecureRandom.uuid
273
391
  end
274
392
 
275
393
  get '/' do
@@ -390,7 +508,7 @@ module Vmpooler
390
508
  end
391
509
 
392
510
  # for backwards compatibility, include separate "empty" stats in "status" block
393
- if ready == 0
511
+ if ready == 0 && max != 0
394
512
  result[:status][:empty] ||= []
395
513
  result[:status][:empty].push(pool['name'])
396
514
 
@@ -684,6 +802,88 @@ module Vmpooler
684
802
  JSON.pretty_generate(result)
685
803
  end
686
804
 
805
+ post "#{api_prefix}/ondemandvm/?" do
806
+ content_type :json
807
+
808
+ need_token! if Vmpooler::API.settings.config[:auth]
809
+
810
+ result = { 'ok' => false }
811
+
812
+ begin
813
+ payload = JSON.parse(request.body.read)
814
+
815
+ if payload
816
+ invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
817
+ if invalid.empty?
818
+ result = generate_ondemand_request(payload)
819
+ else
820
+ result[:bad_templates] = invalid
821
+ invalid.each do |bad_template|
822
+ metrics.increment('ondemandrequest.invalid.' + bad_template)
823
+ end
824
+ status 404
825
+ end
826
+ else
827
+ metrics.increment('ondemandrequest.invalid.unknown')
828
+ status 404
829
+ end
830
+ rescue JSON::ParserError
831
+ status 400
832
+ result = {
833
+ 'ok' => false,
834
+ 'message' => 'JSON payload could not be parsed'
835
+ }
836
+ end
837
+
838
+ JSON.pretty_generate(result)
839
+ end
840
+
841
+ post "#{api_prefix}/ondemandvm/:template/?" do
842
+ content_type :json
843
+ result = { 'ok' => false }
844
+
845
+ need_token! if Vmpooler::API.settings.config[:auth]
846
+
847
+ payload = extract_templates_from_query_params(params[:template])
848
+
849
+ if payload
850
+ invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
851
+ if invalid.empty?
852
+ result = generate_ondemand_request(payload)
853
+ else
854
+ result[:bad_templates] = invalid
855
+ invalid.each do |bad_template|
856
+ metrics.increment('ondemandrequest.invalid.' + bad_template)
857
+ end
858
+ status 404
859
+ end
860
+ else
861
+ metrics.increment('ondemandrequest.invalid.unknown')
862
+ status 404
863
+ end
864
+
865
+ JSON.pretty_generate(result)
866
+ end
867
+
868
+ get "#{api_prefix}/ondemandvm/:requestid/?" do
869
+ content_type :json
870
+
871
+ status 404
872
+ result = check_ondemand_request(params[:requestid])
873
+
874
+ JSON.pretty_generate(result)
875
+ end
876
+
877
+ delete "#{api_prefix}/ondemandvm/:requestid/?" do
878
+ content_type :json
879
+ need_token! if Vmpooler::API.settings.config[:auth]
880
+
881
+ status 404
882
+ result = delete_ondemand_request(params[:requestid])
883
+
884
+ JSON.pretty_generate(result)
885
+ end
886
+
687
887
  post "#{api_prefix}/vm/?" do
688
888
  content_type :json
689
889
  result = { 'ok' => false }
@@ -759,6 +959,78 @@ module Vmpooler
759
959
  invalid
760
960
  end
761
961
 
962
+ def check_ondemand_request(request_id)
963
+ result = { 'ok' => false }
964
+ request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
965
+ if request_hash.empty?
966
+ result['message'] = "no request found for request_id '#{request_id}'"
967
+ return result
968
+ end
969
+
970
+ result['request_id'] = request_id
971
+ result['ready'] = false
972
+ result['ok'] = true
973
+ status 202
974
+
975
+ if request_hash['status'] == 'ready'
976
+ result['ready'] = true
977
+ platform_parts = request_hash['requested'].split(',')
978
+ platform_parts.each do |platform|
979
+ pool_alias, pool, _count = platform.split(':')
980
+ instances = backend.smembers("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
981
+ result[pool_alias] = { 'hostname': instances }
982
+ end
983
+ result['domain'] = config['domain'] if config['domain']
984
+ status 200
985
+ elsif request_hash['status'] == 'failed'
986
+ result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
987
+ status 200
988
+ elsif request_hash['status'] == 'deleted'
989
+ result['message'] = 'The request has been deleted'
990
+ status 200
991
+ else
992
+ platform_parts = request_hash['requested'].split(',')
993
+ platform_parts.each do |platform|
994
+ pool_alias, pool, count = platform.split(':')
995
+ instance_count = backend.scard("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
996
+ result[pool_alias] = {
997
+ 'ready': instance_count.to_s,
998
+ 'pending': (count.to_i - instance_count.to_i).to_s
999
+ }
1000
+ end
1001
+ end
1002
+
1003
+ result
1004
+ end
1005
+
1006
+ def delete_ondemand_request(request_id)
1007
+ result = { 'ok' => false }
1008
+
1009
+ platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
1010
+ unless platforms
1011
+ result['message'] = "no request found for request_id '#{request_id}'"
1012
+ return result
1013
+ end
1014
+
1015
+ if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
1016
+ result['message'] = 'the request has already been deleted'
1017
+ else
1018
+ backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
1019
+
1020
+ platforms.split(',').each do |platform|
1021
+ pool_alias, pool, _count = platform.split(':')
1022
+ backend.smembers("vmpooler__#{request_id}__#{pool_alias}__#{pool}")&.each do |vm|
1023
+ backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
1024
+ end
1025
+ backend.del("vmpooler__#{request_id}__#{pool_alias}__#{pool}")
1026
+ end
1027
+ backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
1028
+ end
1029
+ status 200
1030
+ result['ok'] = true
1031
+ result
1032
+ end
1033
+
762
1034
  post "#{api_prefix}/vm/:template/?" do
763
1035
  content_type :json
764
1036
  result = { 'ok' => false }
@@ -874,6 +1146,8 @@ module Vmpooler
874
1146
 
875
1147
  status 200
876
1148
  result['ok'] = true
1149
+ else
1150
+ metrics.increment('delete.srem.failed')
877
1151
  end
878
1152
  end
879
1153
 
@@ -916,6 +1190,7 @@ module Vmpooler
916
1190
  unless arg.to_i > 0
917
1191
  failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.")
918
1192
  end
1193
+
919
1194
  when 'tags'
920
1195
  unless arg.is_a?(Hash)
921
1196
  failure.push("You provided tags (#{arg}) as something other than a hash.")
@@ -1040,7 +1315,7 @@ module Vmpooler
1040
1315
  invalid.each do |bad_template|
1041
1316
  metrics.increment("config.invalid.#{bad_template}")
1042
1317
  end
1043
- result[:bad_templates] = invalid
1318
+ result[:not_configured] = invalid
1044
1319
  status 400
1045
1320
  end
1046
1321
  else