vmpooler 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -49,13 +49,15 @@ module Vmpooler
49
49
  end
50
50
 
51
51
  def get_template_aliases(template)
52
- result = []
53
- aliases = Vmpooler::API.settings.config[:alias]
54
- if aliases
55
- result += aliases[template] if aliases[template].is_a?(Array)
56
- template_backends << aliases[template] if aliases[template].is_a?(String)
52
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
53
+ result = []
54
+ aliases = Vmpooler::API.settings.config[:alias]
55
+ if aliases
56
+ result += aliases[template] if aliases[template].is_a?(Array)
57
+ template_backends << aliases[template] if aliases[template].is_a?(String)
58
+ end
59
+ result
57
60
  end
58
- result
59
61
  end
60
62
 
61
63
  def get_pool_weights(template_backends)
@@ -109,398 +111,463 @@ module Vmpooler
109
111
  end
110
112
 
111
113
  def fetch_single_vm(template)
112
- template_backends = [template]
113
- aliases = Vmpooler::API.settings.config[:alias]
114
- if aliases
115
- template_backends += aliases[template] if aliases[template].is_a?(Array)
116
- template_backends << aliases[template] if aliases[template].is_a?(String)
117
- pool_index = pool_index(pools)
118
- weighted_pools = {}
119
- template_backends.each do |t|
120
- next unless pool_index.key? t
121
-
122
- index = pool_index[t]
123
- clone_target = pools[index]['clone_target'] || config['clone_target']
124
- next unless config.key?('backend_weight')
125
-
126
- weight = config['backend_weight'][clone_target]
127
- if weight
128
- weighted_pools[t] = weight
114
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
115
+ template_backends = [template]
116
+ aliases = Vmpooler::API.settings.config[:alias]
117
+ if aliases
118
+ template_backends += aliases[template] if aliases[template].is_a?(Array)
119
+ template_backends << aliases[template] if aliases[template].is_a?(String)
120
+ pool_index = pool_index(pools)
121
+ weighted_pools = {}
122
+ template_backends.each do |t|
123
+ next unless pool_index.key? t
124
+
125
+ index = pool_index[t]
126
+ clone_target = pools[index]['clone_target'] || config['clone_target']
127
+ next unless config.key?('backend_weight')
128
+
129
+ weight = config['backend_weight'][clone_target]
130
+ if weight
131
+ weighted_pools[t] = weight
132
+ end
129
133
  end
130
- end
131
134
 
132
- if weighted_pools.count == template_backends.count
133
- pickup = Pickup.new(weighted_pools)
134
- selection = pickup.pick
135
- template_backends.delete(selection)
136
- template_backends.unshift(selection)
137
- else
138
- first = template_backends.sample
139
- template_backends.delete(first)
140
- template_backends.unshift(first)
135
+ if weighted_pools.count == template_backends.count
136
+ pickup = Pickup.new(weighted_pools)
137
+ selection = pickup.pick
138
+ template_backends.delete(selection)
139
+ template_backends.unshift(selection)
140
+ else
141
+ first = template_backends.sample
142
+ template_backends.delete(first)
143
+ template_backends.unshift(first)
144
+ end
141
145
  end
142
- end
143
146
 
144
- checkoutlock.synchronize do
145
- template_backends.each do |template_backend|
146
- vms = backend.smembers("vmpooler__ready__#{template_backend}")
147
- next if vms.empty?
148
-
149
- vms.reverse.each do |vm|
150
- ready = vm_ready?(vm, config['domain'])
151
- if ready
152
- smoved = backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
153
- if smoved
154
- return [vm, template_backend, template]
147
+ checkoutlock.synchronize do
148
+ template_backends.each do |template_backend|
149
+ vms = backend.smembers("vmpooler__ready__#{template_backend}")
150
+ next if vms.empty?
151
+
152
+ vms.reverse.each do |vm|
153
+ ready = vm_ready?(vm, config['domain'])
154
+ if ready
155
+ smoved = backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
156
+ if smoved
157
+ return [vm, template_backend, template]
158
+ else
159
+ metrics.increment("checkout.smove.failed.#{template_backend}")
160
+ return [nil, nil, nil]
161
+ end
155
162
  else
156
- metrics.increment("checkout.smove.failed.#{template_backend}")
157
- return [nil, nil, nil]
163
+ backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__completed__#{template_backend}", vm)
164
+ metrics.increment("checkout.nonresponsive.#{template_backend}")
158
165
  end
159
- else
160
- backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__completed__#{template_backend}", vm)
161
- metrics.increment("checkout.nonresponsive.#{template_backend}")
162
166
  end
163
167
  end
168
+ [nil, nil, nil]
164
169
  end
165
- [nil, nil, nil]
166
170
  end
167
171
  end
168
172
 
169
173
  def return_vm_to_ready_state(template, vm)
170
- backend.srem("vmpooler__migrating__#{template}", vm)
171
- backend.hdel("vmpooler__active__#{template}", vm)
172
- backend.hdel("vmpooler__vm__#{vm}", 'checkout', 'token:token', 'token:user')
173
- backend.smove("vmpooler__running__#{template}", "vmpooler__ready__#{template}", vm)
174
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
175
+ backend.srem("vmpooler__migrating__#{template}", vm)
176
+ backend.hdel("vmpooler__active__#{template}", vm)
177
+ backend.hdel("vmpooler__vm__#{vm}", 'checkout', 'token:token', 'token:user')
178
+ backend.smove("vmpooler__running__#{template}", "vmpooler__ready__#{template}", vm)
179
+ end
174
180
  end
175
181
 
176
182
  def account_for_starting_vm(template, vm)
177
- user = backend.hget("vmpooler__token__#{request.env['HTTP_X_AUTH_TOKEN']}", 'user')
178
- has_token_result = has_token?
179
- backend.sadd("vmpooler__migrating__#{template}", vm)
180
- backend.hset("vmpooler__active__#{template}", vm, Time.now)
181
- backend.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
182
-
183
- if Vmpooler::API.settings.config[:auth] and has_token_result
184
- backend.hset("vmpooler__vm__#{vm}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
185
- backend.hset("vmpooler__vm__#{vm}", 'token:user', user)
186
-
187
- if config['vm_lifetime_auth'].to_i > 0
188
- backend.hset("vmpooler__vm__#{vm}", 'lifetime', config['vm_lifetime_auth'].to_i)
183
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
184
+ user = backend.hget("vmpooler__token__#{request.env['HTTP_X_AUTH_TOKEN']}", 'user')
185
+ span.set_attribute('enduser.id', user)
186
+ has_token_result = has_token?
187
+ backend.sadd("vmpooler__migrating__#{template}", vm)
188
+ backend.hset("vmpooler__active__#{template}", vm, Time.now)
189
+ backend.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
190
+
191
+ if Vmpooler::API.settings.config[:auth] and has_token_result
192
+ backend.hset("vmpooler__vm__#{vm}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
193
+ backend.hset("vmpooler__vm__#{vm}", 'token:user', user)
194
+
195
+ if config['vm_lifetime_auth'].to_i > 0
196
+ backend.hset("vmpooler__vm__#{vm}", 'lifetime', config['vm_lifetime_auth'].to_i)
197
+ end
189
198
  end
190
199
  end
191
200
  end
192
201
 
193
202
  def update_result_hosts(result, template, vm)
194
- result[template] ||= {}
195
- if result[template]['hostname']
196
- result[template]['hostname'] = Array(result[template]['hostname'])
197
- result[template]['hostname'].push(vm)
198
- else
199
- result[template]['hostname'] = vm
203
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
204
+ result[template] ||= {}
205
+ if result[template]['hostname']
206
+ result[template]['hostname'] = Array(result[template]['hostname'])
207
+ result[template]['hostname'].push(vm)
208
+ else
209
+ result[template]['hostname'] = vm
210
+ end
200
211
  end
201
212
  end
202
213
 
203
214
  def atomically_allocate_vms(payload)
204
- result = { 'ok' => false }
205
- failed = false
206
- vms = []
207
-
208
- validate_token(backend) if Vmpooler::API.settings.config[:auth] and has_token?
209
-
210
- payload.each do |requested, count|
211
- count.to_i.times do |_i|
212
- vmname, vmpool, vmtemplate = fetch_single_vm(requested)
213
- if vmname
214
- account_for_starting_vm(vmpool, vmname)
215
- vms << [vmpool, vmname, vmtemplate]
216
- metrics.increment("checkout.success.#{vmpool}")
217
- update_user_metrics('allocate', vmname) if Vmpooler::API.settings.config[:config]['usage_stats']
218
- else
219
- failed = true
220
- metrics.increment("checkout.empty.#{requested}")
221
- break
215
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
216
+ result = { 'ok' => false }
217
+ failed = false
218
+ vms = []
219
+
220
+ validate_token(backend) if Vmpooler::API.settings.config[:auth] and has_token?
221
+
222
+ payload.each do |requested, count|
223
+ count.to_i.times do |_i|
224
+ vmname, vmpool, vmtemplate = fetch_single_vm(requested)
225
+ if vmname
226
+ account_for_starting_vm(vmpool, vmname)
227
+ vms << [vmpool, vmname, vmtemplate]
228
+ metrics.increment("checkout.success.#{vmpool}")
229
+ update_user_metrics('allocate', vmname) if Vmpooler::API.settings.config[:config]['usage_stats']
230
+ else
231
+ failed = true
232
+ metrics.increment("checkout.empty.#{requested}")
233
+ break
234
+ end
222
235
  end
223
236
  end
224
- end
225
237
 
226
- if failed
227
- vms.each do |(vmpool, vmname, _vmtemplate)|
228
- return_vm_to_ready_state(vmpool, vmname)
229
- end
230
- status 503
231
- else
232
- vms.each do |(_vmpool, vmname, vmtemplate)|
233
- update_result_hosts(result, vmtemplate, vmname)
238
+ if failed
239
+ vms.each do |(vmpool, vmname, _vmtemplate)|
240
+ return_vm_to_ready_state(vmpool, vmname)
241
+ end
242
+ span.add_event('error', attributes: {
243
+ 'error.type' => 'Vmpooler::API::V1.atomically_allocate_vms',
244
+ 'error.message' => '503 due to failing to allocate one or more vms'
245
+ })
246
+ status 503
247
+ else
248
+ vm_names = []
249
+ vms.each do |(_vmpool, vmname, vmtemplate)|
250
+ update_result_hosts(result, vmtemplate, vmname)
251
+ vm_names.append(vmname)
252
+ end
253
+
254
+ span.set_attribute('vmpooler.vm_names', vm_names.join(',')) unless vm_names.empty?
255
+
256
+ result['ok'] = true
257
+ result['domain'] = config['domain'] if config['domain']
234
258
  end
235
259
 
236
- result['ok'] = true
237
- result['domain'] = config['domain'] if config['domain']
260
+ result
238
261
  end
239
-
240
- result
241
262
  end
242
263
 
243
264
  def component_to_test(match, labels_string)
244
- return if labels_string.nil?
245
-
246
- labels_string_parts = labels_string.split(',')
247
- labels_string_parts.each do |part|
248
- key, value = part.split('=')
249
- next if value.nil?
250
- return value if key == match
265
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
266
+ return if labels_string.nil?
267
+
268
+ labels_string_parts = labels_string.split(',')
269
+ labels_string_parts.each do |part|
270
+ key, value = part.split('=')
271
+ next if value.nil?
272
+ return value if key == match
273
+ end
274
+ 'none'
251
275
  end
252
- 'none'
253
276
  end
254
277
 
255
278
  def update_user_metrics(operation, vmname)
256
- backend.multi
257
- backend.hget("vmpooler__vm__#{vmname}", 'tag:jenkins_build_url')
258
- backend.hget("vmpooler__vm__#{vmname}", 'token:user')
259
- backend.hget("vmpooler__vm__#{vmname}", 'template')
260
- jenkins_build_url, user, poolname = backend.exec
261
- poolname = poolname.gsub('.', '_')
262
-
263
- if user
264
- user = user.gsub('.', '_')
265
- else
266
- user = 'unauthenticated'
267
- end
268
- metrics.increment("user.#{user}.#{operation}.#{poolname}")
279
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
280
+ begin
281
+ backend.multi
282
+ backend.hget("vmpooler__vm__#{vmname}", 'tag:jenkins_build_url')
283
+ backend.hget("vmpooler__vm__#{vmname}", 'token:user')
284
+ backend.hget("vmpooler__vm__#{vmname}", 'template')
285
+ jenkins_build_url, user, poolname = backend.exec
286
+ poolname = poolname.gsub('.', '_')
287
+
288
+ if user
289
+ user = user.gsub('.', '_')
290
+ else
291
+ user = 'unauthenticated'
292
+ end
293
+ metrics.increment("user.#{user}.#{operation}.#{poolname}")
269
294
 
270
- if jenkins_build_url
271
- if jenkins_build_url.include? 'litmus'
272
- # Very simple filter for Litmus jobs - just count them coming through for the moment.
273
- metrics.increment("usage_litmus.#{user}.#{operation}.#{poolname}")
274
- return
295
+ if jenkins_build_url
296
+ if jenkins_build_url.include? 'litmus'
297
+ # Very simple filter for Litmus jobs - just count them coming through for the moment.
298
+ metrics.increment("usage_litmus.#{user}.#{operation}.#{poolname}")
299
+ else
300
+ url_parts = jenkins_build_url.split('/')[2..-1]
301
+ jenkins_instance = url_parts[0].gsub('.', '_')
302
+ value_stream_parts = url_parts[2].split('_')
303
+ value_stream_parts = value_stream_parts.map { |s| s.gsub('.', '_') }
304
+ value_stream = value_stream_parts.shift
305
+ branch = value_stream_parts.pop
306
+ project = value_stream_parts.shift
307
+ job_name = value_stream_parts.join('_')
308
+ build_metadata_parts = url_parts[3]
309
+ component_to_test = component_to_test('RMM_COMPONENT_TO_TEST_NAME', build_metadata_parts)
310
+
311
+ metrics.increment("usage_jenkins_instance.#{jenkins_instance}.#{value_stream}.#{operation}.#{poolname}")
312
+ metrics.increment("usage_branch_project.#{branch}.#{project}.#{operation}.#{poolname}")
313
+ metrics.increment("usage_job_component.#{job_name}.#{component_to_test}.#{operation}.#{poolname}")
314
+ end
315
+ end
316
+ rescue StandardError => e
317
+ puts 'd', "[!] [#{poolname}] failed while evaluating usage labels on '#{vmname}' with an error: #{e}"
318
+ span.record_exception(e)
319
+ span.status = OpenTelemetry::Trace::Status.error(e.to_s)
320
+ span.add_event('log', attributes: {
321
+ 'log.severity' => 'debug',
322
+ 'log.message' => "[#{poolname}] failed while evaluating usage labels on '#{vmname}' with an error: #{e}"
323
+ })
275
324
  end
276
-
277
- url_parts = jenkins_build_url.split('/')[2..-1]
278
- jenkins_instance = url_parts[0].gsub('.', '_')
279
- value_stream_parts = url_parts[2].split('_')
280
- value_stream_parts = value_stream_parts.map { |s| s.gsub('.', '_') }
281
- value_stream = value_stream_parts.shift
282
- branch = value_stream_parts.pop
283
- project = value_stream_parts.shift
284
- job_name = value_stream_parts.join('_')
285
- build_metadata_parts = url_parts[3]
286
- component_to_test = component_to_test('RMM_COMPONENT_TO_TEST_NAME', build_metadata_parts)
287
-
288
- metrics.increment("usage_jenkins_instance.#{jenkins_instance}.#{value_stream}.#{operation}.#{poolname}")
289
- metrics.increment("usage_branch_project.#{branch}.#{project}.#{operation}.#{poolname}")
290
- metrics.increment("usage_job_component.#{job_name}.#{component_to_test}.#{operation}.#{poolname}")
291
325
  end
292
- rescue StandardError => e
293
- puts 'd', "[!] [#{poolname}] failed while evaluating usage labels on '#{vmname}' with an error: #{e}"
294
326
  end
295
327
 
296
328
  def reset_pool_size(poolname)
297
- result = { 'ok' => false }
329
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
330
+ result = { 'ok' => false }
298
331
 
299
- pool_index = pool_index(pools)
332
+ pool_index = pool_index(pools)
300
333
 
301
- pools_updated = 0
302
- sync_pool_sizes
334
+ pools_updated = 0
335
+ sync_pool_sizes
303
336
 
304
- pool_size_now = pools[pool_index[poolname]]['size'].to_i
305
- pool_size_original = pools_at_startup[pool_index[poolname]]['size'].to_i
306
- result['pool_size_before_reset'] = pool_size_now
307
- result['pool_size_before_overrides'] = pool_size_original
337
+ pool_size_now = pools[pool_index[poolname]]['size'].to_i
338
+ pool_size_original = pools_at_startup[pool_index[poolname]]['size'].to_i
339
+ result['pool_size_before_reset'] = pool_size_now
340
+ result['pool_size_before_overrides'] = pool_size_original
308
341
 
309
- unless pool_size_now == pool_size_original
310
- pools[pool_index[poolname]]['size'] = pool_size_original
311
- backend.hdel('vmpooler__config__poolsize', poolname)
312
- backend.sadd('vmpooler__pool__undo_size_override', poolname)
313
- pools_updated += 1
314
- status 201
315
- end
342
+ unless pool_size_now == pool_size_original
343
+ pools[pool_index[poolname]]['size'] = pool_size_original
344
+ backend.hdel('vmpooler__config__poolsize', poolname)
345
+ backend.sadd('vmpooler__pool__undo_size_override', poolname)
346
+ pools_updated += 1
347
+ status 201
348
+ end
316
349
 
317
- status 200 unless pools_updated > 0
318
- result['ok'] = true
319
- result
350
+ status 200 unless pools_updated > 0
351
+ result['ok'] = true
352
+ result
353
+ end
320
354
  end
321
355
 
322
356
  def update_pool_size(payload)
323
- result = { 'ok' => false }
357
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
358
+ result = { 'ok' => false }
324
359
 
325
- pool_index = pool_index(pools)
326
- pools_updated = 0
327
- sync_pool_sizes
360
+ pool_index = pool_index(pools)
361
+ pools_updated = 0
362
+ sync_pool_sizes
328
363
 
329
- payload.each do |poolname, size|
330
- unless pools[pool_index[poolname]]['size'] == size.to_i
331
- pools[pool_index[poolname]]['size'] = size.to_i
332
- backend.hset('vmpooler__config__poolsize', poolname, size)
333
- pools_updated += 1
334
- status 201
364
+ payload.each do |poolname, size|
365
+ unless pools[pool_index[poolname]]['size'] == size.to_i
366
+ pools[pool_index[poolname]]['size'] = size.to_i
367
+ backend.hset('vmpooler__config__poolsize', poolname, size)
368
+ pools_updated += 1
369
+ status 201
370
+ end
335
371
  end
372
+ status 200 unless pools_updated > 0
373
+ result['ok'] = true
374
+ result
336
375
  end
337
- status 200 unless pools_updated > 0
338
- result['ok'] = true
339
- result
340
376
  end
341
377
 
342
378
  def reset_pool_template(poolname)
343
- result = { 'ok' => false }
379
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
380
+ result = { 'ok' => false }
381
+
382
+ pool_index_live = pool_index(pools)
383
+ pool_index_original = pool_index(pools_at_startup)
344
384
 
345
- pool_index_live = pool_index(pools)
346
- pool_index_original = pool_index(pools_at_startup)
385
+ pools_updated = 0
386
+ sync_pool_templates
347
387
 
348
- pools_updated = 0
349
- sync_pool_templates
388
+ template_now = pools[pool_index_live[poolname]]['template']
389
+ template_original = pools_at_startup[pool_index_original[poolname]]['template']
390
+ result['template_before_reset'] = template_now
391
+ result['template_before_overrides'] = template_original
350
392
 
351
- template_now = pools[pool_index_live[poolname]]['template']
352
- template_original = pools_at_startup[pool_index_original[poolname]]['template']
353
- result['template_before_reset'] = template_now
354
- result['template_before_overrides'] = template_original
393
+ unless template_now == template_original
394
+ pools[pool_index_live[poolname]]['template'] = template_original
395
+ backend.hdel('vmpooler__config__template', poolname)
396
+ backend.sadd('vmpooler__pool__undo_template_override', poolname)
397
+ pools_updated += 1
398
+ status 201
399
+ end
355
400
 
356
- unless template_now == template_original
357
- pools[pool_index_live[poolname]]['template'] = template_original
358
- backend.hdel('vmpooler__config__template', poolname)
359
- backend.sadd('vmpooler__pool__undo_template_override', poolname)
360
- pools_updated += 1
361
- status 201
401
+ status 200 unless pools_updated > 0
402
+ result['ok'] = true
403
+ result
362
404
  end
363
-
364
- status 200 unless pools_updated > 0
365
- result['ok'] = true
366
- result
367
405
  end
368
406
 
369
407
  def update_pool_template(payload)
370
- result = { 'ok' => false }
408
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
409
+ result = { 'ok' => false }
371
410
 
372
- pool_index = pool_index(pools)
373
- pools_updated = 0
374
- sync_pool_templates
411
+ pool_index = pool_index(pools)
412
+ pools_updated = 0
413
+ sync_pool_templates
375
414
 
376
- payload.each do |poolname, template|
377
- unless pools[pool_index[poolname]]['template'] == template
378
- pools[pool_index[poolname]]['template'] = template
379
- backend.hset('vmpooler__config__template', poolname, template)
380
- pools_updated += 1
381
- status 201
415
+ payload.each do |poolname, template|
416
+ unless pools[pool_index[poolname]]['template'] == template
417
+ pools[pool_index[poolname]]['template'] = template
418
+ backend.hset('vmpooler__config__template', poolname, template)
419
+ pools_updated += 1
420
+ status 201
421
+ end
382
422
  end
423
+ status 200 unless pools_updated > 0
424
+ result['ok'] = true
425
+ result
383
426
  end
384
- status 200 unless pools_updated > 0
385
- result['ok'] = true
386
- result
387
427
  end
388
428
 
389
429
  def reset_pool(payload)
390
- result = { 'ok' => false }
430
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
431
+ result = { 'ok' => false }
391
432
 
392
- payload.each do |poolname, _count|
393
- backend.sadd('vmpooler__poolreset', poolname)
433
+ payload.each do |poolname, _count|
434
+ backend.sadd('vmpooler__poolreset', poolname)
435
+ end
436
+ status 201
437
+ result['ok'] = true
438
+ result
394
439
  end
395
- status 201
396
- result['ok'] = true
397
- result
398
440
  end
399
441
 
400
442
  def update_clone_target(payload)
401
- result = { 'ok' => false }
402
-
403
- pool_index = pool_index(pools)
404
- pools_updated = 0
405
- sync_clone_targets
443
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
444
+ result = { 'ok' => false }
406
445
 
407
- payload.each do |poolname, clone_target|
408
- unless pools[pool_index[poolname]]['clone_target'] == clone_target
409
- pools[pool_index[poolname]]['clone_target'] = clone_target
410
- backend.hset('vmpooler__config__clone_target', poolname, clone_target)
411
- pools_updated += 1
412
- status 201
446
+ pool_index = pool_index(pools)
447
+ pools_updated = 0
448
+ sync_clone_targets
449
+
450
+ payload.each do |poolname, clone_target|
451
+ unless pools[pool_index[poolname]]['clone_target'] == clone_target
452
+ pools[pool_index[poolname]]['clone_target'] = clone_target
453
+ backend.hset('vmpooler__config__clone_target', poolname, clone_target)
454
+ pools_updated += 1
455
+ status 201
456
+ end
413
457
  end
458
+ status 200 unless pools_updated > 0
459
+ result['ok'] = true
460
+ result
414
461
  end
415
- status 200 unless pools_updated > 0
416
- result['ok'] = true
417
- result
418
462
  end
419
463
 
420
464
  def sync_pool_templates
421
- pool_index = pool_index(pools)
422
- template_configs = backend.hgetall('vmpooler__config__template')
423
- template_configs&.each do |poolname, template|
424
- next unless pool_index.include? poolname
465
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
466
+ pool_index = pool_index(pools)
467
+ template_configs = backend.hgetall('vmpooler__config__template')
468
+ template_configs&.each do |poolname, template|
469
+ next unless pool_index.include? poolname
425
470
 
426
- pools[pool_index[poolname]]['template'] = template
471
+ pools[pool_index[poolname]]['template'] = template
472
+ end
427
473
  end
428
474
  end
429
475
 
430
476
  def sync_pool_sizes
431
- pool_index = pool_index(pools)
432
- poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
433
- poolsize_configs&.each do |poolname, size|
434
- next unless pool_index.include? poolname
477
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
478
+ pool_index = pool_index(pools)
479
+ poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
480
+ poolsize_configs&.each do |poolname, size|
481
+ next unless pool_index.include? poolname
435
482
 
436
- pools[pool_index[poolname]]['size'] = size.to_i
483
+ pools[pool_index[poolname]]['size'] = size.to_i
484
+ end
437
485
  end
438
486
  end
439
487
 
440
488
  def sync_clone_targets
441
- pool_index = pool_index(pools)
442
- clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
443
- clone_target_configs&.each do |poolname, clone_target|
444
- next unless pool_index.include? poolname
489
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
490
+ pool_index = pool_index(pools)
491
+ clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
492
+ clone_target_configs&.each do |poolname, clone_target|
493
+ next unless pool_index.include? poolname
445
494
 
446
- pools[pool_index[poolname]]['clone_target'] = clone_target
495
+ pools[pool_index[poolname]]['clone_target'] = clone_target
496
+ end
447
497
  end
448
498
  end
449
499
 
450
500
  def too_many_requested?(payload)
451
- payload&.each do |poolname, count|
452
- next unless count.to_i > config['max_ondemand_instances_per_request']
501
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
502
+ payload&.each do |poolname, count|
503
+ next unless count.to_i > config['max_ondemand_instances_per_request']
453
504
 
454
- metrics.increment("ondemandrequest_fail.toomanyrequests.#{poolname}")
455
- return true
505
+ metrics.increment("ondemandrequest_fail.toomanyrequests.#{poolname}")
506
+ return true
507
+ end
508
+ false
456
509
  end
457
- false
458
510
  end
459
511
 
460
512
  def generate_ondemand_request(payload)
461
- result = { 'ok': false }
462
-
463
- requested_instances = payload.reject { |k, _v| k == 'request_id' }
464
- if too_many_requested?(requested_instances)
465
- result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
466
- status 403
467
- return result
468
- end
513
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
514
+ result = { 'ok': false }
515
+
516
+ requested_instances = payload.reject { |k, _v| k == 'request_id' }
517
+ if too_many_requested?(requested_instances)
518
+ e_message = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
519
+ result['message'] = e_message
520
+ status 403
521
+ span.add_event('error', attributes: {
522
+ 'error.type' => 'Vmpooler::API::V1.generate_ondemand_request',
523
+ 'error.message' => "403 due to #{e_message}"
524
+ })
525
+ return result
526
+ end
469
527
 
470
- score = Time.now.to_i
471
- request_id = payload['request_id']
472
- request_id ||= generate_request_id
473
- result['request_id'] = request_id
528
+ score = Time.now.to_i
529
+ request_id = payload['request_id']
530
+ request_id ||= generate_request_id
531
+ result['request_id'] = request_id
532
+ span.set_attribute('vmpooler.request_id', request_id)
533
+
534
+ if backend.exists?("vmpooler__odrequest__#{request_id}")
535
+ e_message = "request_id '#{request_id}' has already been created"
536
+ result['message'] = e_message
537
+ status 409
538
+ span.add_event('error', attributes: {
539
+ 'error.type' => 'Vmpooler::API::V1.generate_ondemand_request',
540
+ 'error.message' => "409 due to #{e_message}"
541
+ })
542
+ metrics.increment('ondemandrequest_generate.duplicaterequests')
543
+ return result
544
+ end
474
545
 
475
- if backend.exists?("vmpooler__odrequest__#{request_id}")
476
- result['message'] = "request_id '#{request_id}' has already been created"
477
- status 409
478
- metrics.increment('ondemandrequest_generate.duplicaterequests')
479
- return result
480
- end
546
+ status 201
481
547
 
482
- status 201
548
+ platforms_with_aliases = []
549
+ requested_instances.each do |poolname, count|
550
+ selection = evaluate_template_aliases(poolname, count)
551
+ selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
552
+ end
553
+ platforms_string = platforms_with_aliases.join(',')
483
554
 
484
- platforms_with_aliases = []
485
- requested_instances.each do |poolname, count|
486
- selection = evaluate_template_aliases(poolname, count)
487
- selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
488
- end
489
- platforms_string = platforms_with_aliases.join(',')
555
+ return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
490
556
 
491
- return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
557
+ backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
558
+ if Vmpooler::API.settings.config[:auth] and has_token?
559
+ token_token = request.env['HTTP_X_AUTH_TOKEN']
560
+ token_user = backend.hget("vmpooler__token__#{token_token}", 'user')
561
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', token_token)
562
+ backend.hset("vmpooler__odrequest__#{request_id}", 'token:user', token_user)
563
+ span.set_attribute('enduser.id', token_user)
564
+ end
492
565
 
493
- backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
494
- if Vmpooler::API.settings.config[:auth] and has_token?
495
- backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
496
- backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
497
- backend.hget("vmpooler__token__#{request.env['HTTP_X_AUTH_TOKEN']}", 'user'))
566
+ result['domain'] = config['domain'] if config['domain']
567
+ result[:ok] = true
568
+ metrics.increment('ondemandrequest_generate.success')
569
+ result
498
570
  end
499
-
500
- result['domain'] = config['domain'] if config['domain']
501
- result[:ok] = true
502
- metrics.increment('ondemandrequest_generate.success')
503
- result
504
571
  end
505
572
 
506
573
  def generate_request_id
@@ -813,6 +880,8 @@ module Vmpooler
813
880
  data = backend.hgetall(key)
814
881
 
815
882
  if data['user'] == Rack::Auth::Basic::Request.new(request.env).username
883
+ span = OpenTelemetry::Trace.current_span
884
+ span.set_attribute('enduser.id', data['user'])
816
885
  token = key.split('__').last
817
886
 
818
887
  result[token] ||= {}
@@ -899,6 +968,8 @@ module Vmpooler
899
968
 
900
969
  backend.hset("vmpooler__token__#{result['token']}", 'user', @auth.username)
901
970
  backend.hset("vmpooler__token__#{result['token']}", 'created', Time.now)
971
+ span = OpenTelemetry::Trace.current_span
972
+ span.set_attribute('enduser.id', @auth.username)
902
973
 
903
974
  status 200
904
975
  result['ok'] = true
@@ -946,6 +1017,8 @@ module Vmpooler
946
1017
  status 404
947
1018
  end
948
1019
  rescue JSON::ParserError
1020
+ span = OpenTelemetry::Trace.current_span
1021
+ span.status = OpenTelemetry::Trace::Status.error('JSON payload could not be parsed')
949
1022
  status 400
950
1023
  result = {
951
1024
  'ok' => false,
@@ -1031,134 +1104,160 @@ module Vmpooler
1031
1104
  end
1032
1105
 
1033
1106
  def extract_templates_from_query_params(params)
1034
- payload = {}
1107
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
1108
+ payload = {}
1035
1109
 
1036
- params.split('+').each do |template|
1037
- payload[template] ||= 0
1038
- payload[template] += 1
1039
- end
1110
+ params.split('+').each do |template|
1111
+ payload[template] ||= 0
1112
+ payload[template] += 1
1113
+ end
1040
1114
 
1041
- payload
1115
+ payload
1116
+ end
1042
1117
  end
1043
1118
 
1044
1119
  def invalid_templates(payload)
1045
- invalid = []
1046
- payload.keys.each do |template|
1047
- invalid << template unless pool_exists?(template)
1120
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
1121
+ invalid = []
1122
+ payload.keys.each do |template|
1123
+ invalid << template unless pool_exists?(template)
1124
+ end
1125
+ invalid
1048
1126
  end
1049
- invalid
1050
1127
  end
1051
1128
 
1052
1129
  def invalid_template_or_size(payload)
1053
- invalid = []
1054
- payload.each do |pool, size|
1055
- invalid << pool unless pool_exists?(pool)
1056
- unless is_integer?(size)
1057
- invalid << pool
1058
- next
1130
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
1131
+ invalid = []
1132
+ payload.each do |pool, size|
1133
+ invalid << pool unless pool_exists?(pool)
1134
+ unless is_integer?(size)
1135
+ invalid << pool
1136
+ next
1137
+ end
1138
+ invalid << pool unless Integer(size) >= 0
1059
1139
  end
1060
- invalid << pool unless Integer(size) >= 0
1140
+ invalid
1061
1141
  end
1062
- invalid
1063
1142
  end
1064
1143
 
1065
1144
  def invalid_template_or_path(payload)
1066
- invalid = []
1067
- payload.each do |pool, template|
1068
- invalid << pool unless pool_exists?(pool)
1069
- invalid << pool unless template.include? '/'
1070
- invalid << pool if template[0] == '/'
1071
- invalid << pool if template[-1] == '/'
1145
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
1146
+ invalid = []
1147
+ payload.each do |pool, template|
1148
+ invalid << pool unless pool_exists?(pool)
1149
+ invalid << pool unless template.include? '/'
1150
+ invalid << pool if template[0] == '/'
1151
+ invalid << pool if template[-1] == '/'
1152
+ end
1153
+ invalid
1072
1154
  end
1073
- invalid
1074
1155
  end
1075
1156
 
1076
1157
  def invalid_pool(payload)
1077
- invalid = []
1078
- payload.each do |pool, _clone_target|
1079
- invalid << pool unless pool_exists?(pool)
1158
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do
1159
+ invalid = []
1160
+ payload.each do |pool, _clone_target|
1161
+ invalid << pool unless pool_exists?(pool)
1162
+ end
1163
+ invalid
1080
1164
  end
1081
- invalid
1082
1165
  end
1083
1166
 
1084
1167
  def check_ondemand_request(request_id)
1085
- result = { 'ok' => false }
1086
- request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
1087
- if request_hash.empty?
1088
- result['message'] = "no request found for request_id '#{request_id}'"
1089
- return result
1090
- end
1168
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
1169
+ span.set_attribute('vmpooler.request_id', request_id)
1170
+ result = { 'ok' => false }
1171
+ request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
1172
+ if request_hash.empty?
1173
+ e_message = "no request found for request_id '#{request_id}'"
1174
+ result['message'] = e_message
1175
+ span.add_event('error', attributes: {
1176
+ 'error.type' => 'Vmpooler::API::V1.check_ondemand_request',
1177
+ 'error.message' => e_message
1178
+ })
1179
+ return result
1180
+ end
1091
1181
 
1092
- result['request_id'] = request_id
1093
- result['ready'] = false
1094
- result['ok'] = true
1095
- status 202
1182
+ result['request_id'] = request_id
1183
+ result['ready'] = false
1184
+ result['ok'] = true
1185
+ status 202
1096
1186
 
1097
- case request_hash['status']
1098
- when 'ready'
1099
- result['ready'] = true
1100
- Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
1101
- instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1187
+ case request_hash['status']
1188
+ when 'ready'
1189
+ result['ready'] = true
1190
+ Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
1191
+ instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1102
1192
 
1103
- if result.key?(platform_alias)
1104
- result[platform_alias][:hostname] = result[platform_alias][:hostname] + instances
1105
- else
1106
- result[platform_alias] = { 'hostname': instances }
1193
+ if result.key?(platform_alias)
1194
+ result[platform_alias][:hostname] = result[platform_alias][:hostname] + instances
1195
+ else
1196
+ result[platform_alias] = { 'hostname': instances }
1197
+ end
1107
1198
  end
1108
- end
1109
- result['domain'] = config['domain'] if config['domain']
1110
- status 200
1111
- when 'failed'
1112
- result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
1113
- status 200
1114
- when 'deleted'
1115
- result['message'] = 'The request has been deleted'
1116
- status 200
1117
- else
1118
- Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
1119
- instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1120
- instances_pending = count.to_i - instance_count.to_i
1199
+ result['domain'] = config['domain'] if config['domain']
1200
+ status 200
1201
+ when 'failed'
1202
+ result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
1203
+ status 200
1204
+ when 'deleted'
1205
+ result['message'] = 'The request has been deleted'
1206
+ status 200
1207
+ else
1208
+ Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
1209
+ instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1210
+ instances_pending = count.to_i - instance_count.to_i
1121
1211
 
1122
- if result.key?(platform_alias) && result[platform_alias].key?(:ready)
1123
- result[platform_alias][:ready] = (result[platform_alias][:ready].to_i + instance_count).to_s
1124
- result[platform_alias][:pending] = (result[platform_alias][:pending].to_i + instances_pending).to_s
1125
- else
1126
- result[platform_alias] = {
1127
- 'ready': instance_count.to_s,
1128
- 'pending': instances_pending.to_s
1129
- }
1212
+ if result.key?(platform_alias) && result[platform_alias].key?(:ready)
1213
+ result[platform_alias][:ready] = (result[platform_alias][:ready].to_i + instance_count).to_s
1214
+ result[platform_alias][:pending] = (result[platform_alias][:pending].to_i + instances_pending).to_s
1215
+ else
1216
+ result[platform_alias] = {
1217
+ 'ready': instance_count.to_s,
1218
+ 'pending': instances_pending.to_s
1219
+ }
1220
+ end
1130
1221
  end
1131
1222
  end
1132
- end
1133
1223
 
1134
- result
1224
+ result
1225
+ end
1135
1226
  end
1136
1227
 
1137
1228
  def delete_ondemand_request(request_id)
1138
- result = { 'ok' => false }
1139
-
1140
- platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
1141
- unless platforms
1142
- result['message'] = "no request found for request_id '#{request_id}'"
1143
- return result
1144
- end
1229
+ tracer.in_span("Vmpooler::API::V1.#{__method__}") do |span|
1230
+ span.set_attribute('vmpooler.request_id', request_id)
1231
+ result = { 'ok' => false }
1232
+
1233
+ platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
1234
+ unless platforms
1235
+ e_message = "no request found for request_id '#{request_id}'"
1236
+ result['message'] = e_message
1237
+ span.add_event('error', attributes: {
1238
+ 'error.type' => 'Vmpooler::API::V1.delete_ondemand_request',
1239
+ 'error.message' => e_message
1240
+ })
1241
+ return result
1242
+ end
1145
1243
 
1146
- if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
1147
- result['message'] = 'the request has already been deleted'
1148
- else
1149
- backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
1244
+ if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
1245
+ result['message'] = 'the request has already been deleted'
1246
+ else
1247
+ backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
1150
1248
 
1151
- Parsing.get_platform_pool_count(platforms) do |platform_alias, pool, _count|
1152
- backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")&.each do |vm|
1153
- backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
1249
+ Parsing.get_platform_pool_count(platforms) do |platform_alias, pool, _count|
1250
+ backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")&.each do |vm|
1251
+ backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
1252
+ end
1253
+ backend.del("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1154
1254
  end
1155
- backend.del("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
1255
+ backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
1156
1256
  end
1157
- backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
1257
+ status 200
1258
+ result['ok'] = true
1259
+ result
1158
1260
  end
1159
- status 200
1160
- result['ok'] = true
1161
- result
1162
1261
  end
1163
1262
 
1164
1263
  post "#{api_prefix}/vm/:template/?" do
@@ -1303,7 +1402,10 @@ module Vmpooler
1303
1402
  if backend.exists?("vmpooler__vm__#{params[:hostname]}")
1304
1403
  begin
1305
1404
  jdata = JSON.parse(request.body.read)
1306
- rescue StandardError
1405
+ rescue StandardError => e
1406
+ span = OpenTelemetry::Trace.current_span
1407
+ span.record_exception(e)
1408
+ span.status = OpenTelemetry::Trace::Status.error(e.to_s)
1307
1409
  halt 400, JSON.pretty_generate(result)
1308
1410
  end
1309
1411
 
@@ -1559,6 +1661,9 @@ module Vmpooler
1559
1661
  status 404
1560
1662
  end
1561
1663
  rescue JSON::ParserError
1664
+ span = OpenTelemetry::Trace.current_span
1665
+ span.record_exception(e)
1666
+ span.status = OpenTelemetry::Trace::Status.error('JSON payload could not be parsed')
1562
1667
  status 400
1563
1668
  result = {
1564
1669
  'ok' => false,