mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 10:08:40 -05:00
1525 lines
45 KiB
Ruby
1525 lines
45 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'vmpooler/util/parsing'
|
|
|
|
module Vmpooler
|
|
class API
|
|
class V1 < Sinatra::Base
|
|
api_version = '1'
|
|
api_prefix = "/api/v#{api_version}"
|
|
|
|
helpers do
|
|
include Vmpooler::API::Helpers
|
|
end
|
|
|
|
def backend
|
|
Vmpooler::API.settings.redis
|
|
end
|
|
|
|
def metrics
|
|
Vmpooler::API.settings.metrics
|
|
end
|
|
|
|
def config
|
|
Vmpooler::API.settings.config[:config]
|
|
end
|
|
|
|
def pools
|
|
Vmpooler::API.settings.config[:pools]
|
|
end
|
|
|
|
def pool_exists?(template)
|
|
Vmpooler::API.settings.config[:pool_names].include?(template)
|
|
end
|
|
|
|
def need_auth!
|
|
validate_auth(backend)
|
|
end
|
|
|
|
def need_token!
|
|
validate_token(backend)
|
|
end
|
|
|
|
def checkoutlock
|
|
Vmpooler::API.settings.checkoutlock
|
|
end
|
|
|
|
def get_template_aliases(template)
|
|
result = []
|
|
aliases = Vmpooler::API.settings.config[:alias]
|
|
if aliases
|
|
result += aliases[template] if aliases[template].is_a?(Array)
|
|
template_backends << aliases[template] if aliases[template].is_a?(String)
|
|
end
|
|
result
|
|
end
|
|
|
|
def get_pool_weights(template_backends)
|
|
pool_index = pool_index(pools)
|
|
weighted_pools = {}
|
|
template_backends.each do |t|
|
|
next unless pool_index.key? t
|
|
|
|
index = pool_index[t]
|
|
clone_target = pools[index]['clone_target'] || config['clone_target']
|
|
next unless config.key?('backend_weight')
|
|
|
|
weight = config['backend_weight'][clone_target]
|
|
if weight
|
|
weighted_pools[t] = weight
|
|
end
|
|
end
|
|
weighted_pools
|
|
end
|
|
|
|
def count_selection(selection)
|
|
result = {}
|
|
selection.uniq.each do |poolname|
|
|
result[poolname] = selection.count(poolname)
|
|
end
|
|
result
|
|
end
|
|
|
|
def evaluate_template_aliases(template, count)
|
|
template_backends = []
|
|
template_backends << template if backend.sismember('vmpooler__pools', template)
|
|
selection = []
|
|
aliases = get_template_aliases(template)
|
|
if aliases
|
|
template_backends += aliases
|
|
weighted_pools = get_pool_weights(template_backends)
|
|
|
|
if weighted_pools.count > 1 && weighted_pools.count == template_backends.count
|
|
pickup = Pickup.new(weighted_pools)
|
|
count.to_i.times do
|
|
selection << pickup.pick
|
|
end
|
|
else
|
|
count.to_i.times do
|
|
selection << template_backends.sample
|
|
end
|
|
end
|
|
end
|
|
|
|
count_selection(selection)
|
|
end
|
|
|
|
def fetch_single_vm(template)
|
|
template_backends = [template]
|
|
aliases = Vmpooler::API.settings.config[:alias]
|
|
if aliases
|
|
template_backends += aliases[template] if aliases[template].is_a?(Array)
|
|
template_backends << aliases[template] if aliases[template].is_a?(String)
|
|
pool_index = pool_index(pools)
|
|
weighted_pools = {}
|
|
template_backends.each do |t|
|
|
next unless pool_index.key? t
|
|
|
|
index = pool_index[t]
|
|
clone_target = pools[index]['clone_target'] || config['clone_target']
|
|
next unless config.key?('backend_weight')
|
|
|
|
weight = config['backend_weight'][clone_target]
|
|
if weight
|
|
weighted_pools[t] = weight
|
|
end
|
|
end
|
|
|
|
if weighted_pools.count == template_backends.count
|
|
pickup = Pickup.new(weighted_pools)
|
|
selection = pickup.pick
|
|
template_backends.delete(selection)
|
|
template_backends.unshift(selection)
|
|
else
|
|
first = template_backends.sample
|
|
template_backends.delete(first)
|
|
template_backends.unshift(first)
|
|
end
|
|
end
|
|
|
|
checkoutlock.synchronize do
|
|
template_backends.each do |template_backend|
|
|
vms = backend.smembers("vmpooler__ready__#{template_backend}")
|
|
next if vms.empty?
|
|
|
|
vms.reverse.each do |vm|
|
|
ready = vm_ready?(vm, config['domain'])
|
|
if ready
|
|
smoved = backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
|
|
if smoved
|
|
return [vm, template_backend, template]
|
|
else
|
|
metrics.increment("checkout.smove.failed.#{template_backend}")
|
|
return [nil, nil, nil]
|
|
end
|
|
else
|
|
backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__completed__#{template_backend}", vm)
|
|
metrics.increment("checkout.nonresponsive.#{template_backend}")
|
|
end
|
|
end
|
|
end
|
|
[nil, nil, nil]
|
|
end
|
|
end
|
|
|
|
def return_vm_to_ready_state(template, vm)
|
|
backend.srem("vmpooler__migrating__#{template}", vm)
|
|
backend.hdel("vmpooler__active__#{template}", vm)
|
|
backend.hdel("vmpooler__vm__#{vm}", 'checkout', 'token:token', 'token:user')
|
|
backend.smove("vmpooler__running__#{template}", "vmpooler__ready__#{template}", vm)
|
|
end
|
|
|
|
def account_for_starting_vm(template, vm)
|
|
user = backend.hget("vmpooler__token__#{request.env['HTTP_X_AUTH_TOKEN']}", 'user')
|
|
has_token_result = has_token?
|
|
backend.sadd("vmpooler__migrating__#{template}", vm)
|
|
backend.hset("vmpooler__active__#{template}", vm, Time.now)
|
|
backend.hset("vmpooler__vm__#{vm}", 'checkout', Time.now)
|
|
|
|
if Vmpooler::API.settings.config[:auth] and has_token_result
|
|
backend.hset("vmpooler__vm__#{vm}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
|
backend.hset("vmpooler__vm__#{vm}", 'token:user', user)
|
|
|
|
if config['vm_lifetime_auth'].to_i > 0
|
|
backend.hset("vmpooler__vm__#{vm}", 'lifetime', config['vm_lifetime_auth'].to_i)
|
|
end
|
|
end
|
|
end
|
|
|
|
def update_result_hosts(result, template, vm)
|
|
result[template] ||= {}
|
|
if result[template]['hostname']
|
|
result[template]['hostname'] = Array(result[template]['hostname'])
|
|
result[template]['hostname'].push(vm)
|
|
else
|
|
result[template]['hostname'] = vm
|
|
end
|
|
end
|
|
|
|
def atomically_allocate_vms(payload)
|
|
result = { 'ok' => false }
|
|
failed = false
|
|
vms = []
|
|
|
|
validate_token(backend) if Vmpooler::API.settings.config[:auth] and has_token?
|
|
|
|
payload.each do |requested, count|
|
|
count.to_i.times do |_i|
|
|
vmname, vmpool, vmtemplate = fetch_single_vm(requested)
|
|
if vmname
|
|
account_for_starting_vm(vmpool, vmname)
|
|
vms << [vmpool, vmname, vmtemplate]
|
|
metrics.increment("checkout.success.#{vmpool}")
|
|
update_user_metrics('allocate', vmname) if Vmpooler::API.settings.config[:config]['usage_stats']
|
|
else
|
|
failed = true
|
|
metrics.increment("checkout.empty.#{requested}")
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if failed
|
|
vms.each do |(vmpool, vmname, _vmtemplate)|
|
|
return_vm_to_ready_state(vmpool, vmname)
|
|
end
|
|
status 503
|
|
else
|
|
vms.each do |(_vmpool, vmname, vmtemplate)|
|
|
update_result_hosts(result, vmtemplate, vmname)
|
|
end
|
|
|
|
result['ok'] = true
|
|
result['domain'] = config['domain'] if config['domain']
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def update_user_metrics(operation, vmname)
|
|
backend.multi
|
|
backend.hget("vmpooler__vm__#{vmname}", 'tag:jenkins_build_url')
|
|
backend.hget("vmpooler__vm__#{vmname}", 'token:user')
|
|
backend.hget("vmpooler__vm__#{vmname}", 'template')
|
|
jenkins_build_url, user, poolname = backend.exec
|
|
poolname = poolname.gsub('.', '_')
|
|
|
|
if user
|
|
user = user.gsub('.', '_')
|
|
else
|
|
user = 'unauthenticated'
|
|
end
|
|
metrics.increment("user.#{user}.#{operation}.#{poolname}")
|
|
|
|
if jenkins_build_url
|
|
if jenkins_build_url.include? 'litmus'
|
|
# Very simple filter for Litmus jobs - just count them coming through for the moment.
|
|
metrics.increment("usage_litmus.#{user}.#{operation}.#{poolname}")
|
|
return
|
|
end
|
|
|
|
url_parts = jenkins_build_url.split('/')[2..-1]
|
|
jenkins_instance = url_parts[0].gsub('.', '_')
|
|
value_stream_parts = url_parts[2].split('_')
|
|
value_stream_parts = value_stream_parts.map { |s| s.gsub('.', '_') }
|
|
value_stream = value_stream_parts.shift
|
|
branch = value_stream_parts.pop
|
|
project = value_stream_parts.shift
|
|
job_name = value_stream_parts.join('_')
|
|
build_metadata_parts = url_parts[3]
|
|
component_to_test = component_to_test('RMM_COMPONENT_TO_TEST_NAME', build_metadata_parts)
|
|
|
|
metrics.increment("usage_jenkins_instance.#{jenkins_instance}.#{value_stream}.#{operation}.#{poolname}")
|
|
metrics.increment("usage_branch_project.#{branch}.#{project}.#{operation}.#{poolname}")
|
|
metrics.increment("usage_job_component.#{job_name}.#{component_to_test}.#{operation}.#{poolname}")
|
|
end
|
|
rescue StandardError => e
|
|
puts 'd', "[!] [#{poolname}] failed while evaluating usage labels on '#{vmname}' with an error: #{e}"
|
|
end
|
|
|
|
def update_pool_size(payload)
|
|
result = { 'ok' => false }
|
|
|
|
pool_index = pool_index(pools)
|
|
pools_updated = 0
|
|
sync_pool_sizes
|
|
|
|
payload.each do |poolname, size|
|
|
unless pools[pool_index[poolname]]['size'] == size.to_i
|
|
pools[pool_index[poolname]]['size'] = size.to_i
|
|
backend.hset('vmpooler__config__poolsize', poolname, size)
|
|
pools_updated += 1
|
|
status 201
|
|
end
|
|
end
|
|
status 200 unless pools_updated > 0
|
|
result['ok'] = true
|
|
result
|
|
end
|
|
|
|
def update_pool_template(payload)
|
|
result = { 'ok' => false }
|
|
|
|
pool_index = pool_index(pools)
|
|
pools_updated = 0
|
|
sync_pool_templates
|
|
|
|
payload.each do |poolname, template|
|
|
unless pools[pool_index[poolname]]['template'] == template
|
|
pools[pool_index[poolname]]['template'] = template
|
|
backend.hset('vmpooler__config__template', poolname, template)
|
|
pools_updated += 1
|
|
status 201
|
|
end
|
|
end
|
|
status 200 unless pools_updated > 0
|
|
result['ok'] = true
|
|
result
|
|
end
|
|
|
|
def reset_pool(payload)
|
|
result = { 'ok' => false }
|
|
|
|
payload.each do |poolname, _count|
|
|
backend.sadd('vmpooler__poolreset', poolname)
|
|
end
|
|
status 201
|
|
result['ok'] = true
|
|
result
|
|
end
|
|
|
|
def update_clone_target(payload)
|
|
result = { 'ok' => false }
|
|
|
|
pool_index = pool_index(pools)
|
|
pools_updated = 0
|
|
sync_clone_targets
|
|
|
|
payload.each do |poolname, clone_target|
|
|
unless pools[pool_index[poolname]]['clone_target'] == clone_target
|
|
pools[pool_index[poolname]]['clone_target'] = clone_target
|
|
backend.hset('vmpooler__config__clone_target', poolname, clone_target)
|
|
pools_updated += 1
|
|
status 201
|
|
end
|
|
end
|
|
status 200 unless pools_updated > 0
|
|
result['ok'] = true
|
|
result
|
|
end
|
|
|
|
def sync_pool_templates
|
|
pool_index = pool_index(pools)
|
|
template_configs = backend.hgetall('vmpooler__config__template')
|
|
template_configs&.each do |poolname, template|
|
|
next unless pool_index.include? poolname
|
|
|
|
pools[pool_index[poolname]]['template'] = template
|
|
end
|
|
end
|
|
|
|
def sync_pool_sizes
|
|
pool_index = pool_index(pools)
|
|
poolsize_configs = backend.hgetall('vmpooler__config__poolsize')
|
|
poolsize_configs&.each do |poolname, size|
|
|
next unless pool_index.include? poolname
|
|
|
|
pools[pool_index[poolname]]['size'] = size.to_i
|
|
end
|
|
end
|
|
|
|
def sync_clone_targets
|
|
pool_index = pool_index(pools)
|
|
clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
|
|
clone_target_configs&.each do |poolname, clone_target|
|
|
next unless pool_index.include? poolname
|
|
|
|
pools[pool_index[poolname]]['clone_target'] = clone_target
|
|
end
|
|
end
|
|
|
|
def too_many_requested?(payload)
|
|
payload&.each do |poolname, count|
|
|
next unless count.to_i > config['max_ondemand_instances_per_request']
|
|
|
|
metrics.increment("ondemandrequest_fail.toomanyrequests.#{poolname}")
|
|
return true
|
|
end
|
|
false
|
|
end
|
|
|
|
def generate_ondemand_request(payload)
|
|
result = { 'ok': false }
|
|
|
|
requested_instances = payload.reject { |k, _v| k == 'request_id' }
|
|
if too_many_requested?(requested_instances)
|
|
result['message'] = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
|
|
status 403
|
|
return result
|
|
end
|
|
|
|
score = Time.now.to_i
|
|
request_id = payload['request_id']
|
|
request_id ||= generate_request_id
|
|
result['request_id'] = request_id
|
|
|
|
if backend.exists?("vmpooler__odrequest__#{request_id}")
|
|
result['message'] = "request_id '#{request_id}' has already been created"
|
|
status 409
|
|
metrics.increment('ondemandrequest_generate.duplicaterequests')
|
|
return result
|
|
end
|
|
|
|
status 201
|
|
|
|
platforms_with_aliases = []
|
|
requested_instances.each do |poolname, count|
|
|
selection = evaluate_template_aliases(poolname, count)
|
|
selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
|
|
end
|
|
platforms_string = platforms_with_aliases.join(',')
|
|
|
|
return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
|
|
|
|
backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
|
|
if Vmpooler::API.settings.config[:auth] and has_token?
|
|
backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
|
backend.hset("vmpooler__odrequest__#{request_id}", 'token:user',
|
|
backend.hget("vmpooler__token__#{request.env['HTTP_X_AUTH_TOKEN']}", 'user'))
|
|
end
|
|
|
|
result['domain'] = config['domain'] if config['domain']
|
|
result[:ok] = true
|
|
metrics.increment('ondemandrequest_generate.success')
|
|
result
|
|
end
|
|
|
|
def generate_request_id
|
|
SecureRandom.uuid
|
|
end
|
|
|
|
get '/' do
|
|
sync_pool_sizes
|
|
redirect to('/dashboard/')
|
|
end
|
|
|
|
# Provide run-time statistics
|
|
#
|
|
# Example:
|
|
#
|
|
# {
|
|
# "boot": {
|
|
# "duration": {
|
|
# "average": 163.6,
|
|
# "min": 65.49,
|
|
# "max": 830.07,
|
|
# "total": 247744.71000000002
|
|
# },
|
|
# "count": {
|
|
# "total": 1514
|
|
# }
|
|
# },
|
|
# "capacity": {
|
|
# "current": 968,
|
|
# "total": 975,
|
|
# "percent": 99.3
|
|
# },
|
|
# "clone": {
|
|
# "duration": {
|
|
# "average": 17.0,
|
|
# "min": 4.66,
|
|
# "max": 637.96,
|
|
# "total": 25634.15
|
|
# },
|
|
# "count": {
|
|
# "total": 1507
|
|
# }
|
|
# },
|
|
# "queue": {
|
|
# "pending": 12,
|
|
# "cloning": 0,
|
|
# "booting": 12,
|
|
# "ready": 968,
|
|
# "running": 367,
|
|
# "completed": 0,
|
|
# "total": 1347
|
|
# },
|
|
# "pools": {
|
|
# "ready": 100,
|
|
# "running": 120,
|
|
# "pending": 5,
|
|
# "max": 250,
|
|
# }
|
|
# "status": {
|
|
# "ok": true,
|
|
# "message": "Battle station fully armed and operational.",
|
|
# "empty": [ # NOTE: would not have 'ok: true' w/ "empty" pools
|
|
# "redhat-7-x86_64",
|
|
# "ubuntu-1404-i386"
|
|
# ],
|
|
# "uptime": 179585.9
|
|
# }
|
|
#
|
|
# If the query parameter 'view' is provided, it will be used to select which top level
|
|
# element to compute and return. Select them by specifying them in a comma separated list.
|
|
# For example /status?view=capacity,boot
|
|
# would return only the "capacity" and "boot" statistics. "status" is always returned
|
|
|
|
get "#{api_prefix}/status/?" do
|
|
content_type :json
|
|
|
|
if params[:view]
|
|
views = params[:view].split(",")
|
|
end
|
|
|
|
result = {
|
|
status: {
|
|
ok: true,
|
|
message: 'Battle station fully armed and operational.'
|
|
}
|
|
}
|
|
|
|
sync_pool_sizes
|
|
|
|
result[:capacity] = get_capacity_metrics(pools, backend) unless views and not views.include?("capacity")
|
|
result[:queue] = get_queue_metrics(pools, backend) unless views and not views.include?("queue")
|
|
result[:clone] = get_task_metrics(backend, 'clone', Date.today.to_s) unless views and not views.include?("clone")
|
|
result[:boot] = get_task_metrics(backend, 'boot', Date.today.to_s) unless views and not views.include?("boot")
|
|
|
|
# Check for empty pools
|
|
result[:pools] = {} unless views and not views.include?("pools")
|
|
ready_hash = get_list_across_pools_redis_scard(pools, 'vmpooler__ready__', backend)
|
|
running_hash = get_list_across_pools_redis_scard(pools, 'vmpooler__running__', backend)
|
|
pending_hash = get_list_across_pools_redis_scard(pools, 'vmpooler__pending__', backend)
|
|
lastBoot_hash = get_list_across_pools_redis_hget(pools, 'vmpooler__lastboot', backend)
|
|
|
|
unless views and not views.include?("pools")
|
|
pools.each do |pool|
|
|
# REMIND: move this out of the API and into the back-end
|
|
ready = ready_hash[pool['name']]
|
|
running = running_hash[pool['name']]
|
|
pending = pending_hash[pool['name']]
|
|
max = pool['size']
|
|
lastBoot = lastBoot_hash[pool['name']]
|
|
aka = pool['alias']
|
|
|
|
result[:pools][pool['name']] = {
|
|
ready: ready,
|
|
running: running,
|
|
pending: pending,
|
|
max: max,
|
|
lastBoot: lastBoot
|
|
}
|
|
|
|
if aka
|
|
result[:pools][pool['name']][:alias] = aka
|
|
end
|
|
|
|
# for backwards compatibility, include separate "empty" stats in "status" block
|
|
if ready == 0 && max != 0
|
|
result[:status][:empty] ||= []
|
|
result[:status][:empty].push(pool['name'])
|
|
|
|
result[:status][:ok] = false
|
|
result[:status][:message] = "Found #{result[:status][:empty].length} empty pools."
|
|
end
|
|
end
|
|
end
|
|
|
|
result[:status][:uptime] = (Time.now - Vmpooler::API.settings.config[:uptime]).round(1) if Vmpooler::API.settings.config[:uptime]
|
|
|
|
JSON.pretty_generate(Hash[result.sort_by { |k, _v| k }])
|
|
end
|
|
|
|
# request statistics for specific pools by passing parameter 'pool'
|
|
# with a coma separated list of pools we want to query ?pool=ABC,DEF
|
|
# returns the ready, max numbers and the aliases (if set)
|
|
get "#{api_prefix}/poolstat/?" do
|
|
content_type :json
|
|
|
|
result = {}
|
|
|
|
poolscopy = []
|
|
|
|
if params[:pool]
|
|
subpool = params[:pool].split(",")
|
|
poolscopy = pools.select do |p|
|
|
if subpool.include?(p['name'])
|
|
true
|
|
elsif !p['alias'].nil?
|
|
if p['alias'].instance_of?(Array)
|
|
(p['alias'] & subpool).any?
|
|
elsif p['alias'].instance_of?(String)
|
|
subpool.include?(p['alias'])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
result[:pools] = {}
|
|
|
|
poolscopy.each do |pool|
|
|
result[:pools][pool['name']] = {}
|
|
|
|
max = pool['size']
|
|
aka = pool['alias']
|
|
|
|
result[:pools][pool['name']][:max] = max
|
|
|
|
if aka
|
|
result[:pools][pool['name']][:alias] = aka
|
|
end
|
|
end
|
|
|
|
ready_hash = get_list_across_pools_redis_scard(poolscopy, 'vmpooler__ready__', backend)
|
|
|
|
ready_hash.each { |k, v| result[:pools][k][:ready] = v }
|
|
|
|
JSON.pretty_generate(Hash[result.sort_by { |k, _v| k }])
|
|
end
|
|
|
|
# requests the total number of running VMs
|
|
get "#{api_prefix}/totalrunning/?" do
|
|
content_type :json
|
|
queue = {
|
|
running: 0
|
|
}
|
|
|
|
queue[:running] = get_total_across_pools_redis_scard(pools, 'vmpooler__running__', backend)
|
|
|
|
JSON.pretty_generate(queue)
|
|
end
|
|
|
|
get "#{api_prefix}/summary/?" do
|
|
content_type :json
|
|
|
|
result = {
|
|
daily: []
|
|
}
|
|
|
|
from_param = params[:from] || Date.today.to_s
|
|
to_param = params[:to] || Date.today.to_s
|
|
|
|
# Validate date formats
|
|
[from_param, to_param].each do |param|
|
|
if !validate_date_str(param.to_s)
|
|
halt 400, "Invalid date format '#{param}', must match YYYY-MM-DD."
|
|
end
|
|
end
|
|
|
|
from_date, to_date = Date.parse(from_param), Date.parse(to_param)
|
|
|
|
if to_date < from_date
|
|
halt 400, 'Date range is invalid, \'to\' cannot come before \'from\'.'
|
|
elsif from_date > Date.today
|
|
halt 400, 'Date range is invalid, \'from\' must be in the past.'
|
|
end
|
|
|
|
boot = get_task_summary(backend, 'boot', from_date, to_date, :bypool => true)
|
|
clone = get_task_summary(backend, 'clone', from_date, to_date, :bypool => true)
|
|
tag = get_tag_summary(backend, from_date, to_date)
|
|
|
|
result[:boot] = boot[:boot]
|
|
result[:clone] = clone[:clone]
|
|
result[:tag] = tag[:tag]
|
|
|
|
daily = {}
|
|
|
|
boot[:daily].each do |day|
|
|
daily[day[:date]] ||= {}
|
|
daily[day[:date]][:boot] = day[:boot]
|
|
end
|
|
|
|
clone[:daily].each do |day|
|
|
daily[day[:date]] ||= {}
|
|
daily[day[:date]][:clone] = day[:clone]
|
|
end
|
|
|
|
tag[:daily].each do |day|
|
|
daily[day[:date]] ||= {}
|
|
daily[day[:date]][:tag] = day[:tag]
|
|
end
|
|
|
|
daily.each_key do |day|
|
|
result[:daily].push({
|
|
date: day,
|
|
boot: daily[day][:boot],
|
|
clone: daily[day][:clone],
|
|
tag: daily[day][:tag]
|
|
})
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/summary/:route/?:key?/?" do
|
|
content_type :json
|
|
|
|
result = {}
|
|
|
|
from_param = params[:from] || Date.today.to_s
|
|
to_param = params[:to] || Date.today.to_s
|
|
|
|
# Validate date formats
|
|
[from_param, to_param].each do |param|
|
|
if !validate_date_str(param.to_s)
|
|
halt 400, "Invalid date format '#{param}', must match YYYY-MM-DD."
|
|
end
|
|
end
|
|
|
|
from_date, to_date = Date.parse(from_param), Date.parse(to_param)
|
|
|
|
if to_date < from_date
|
|
halt 400, 'Date range is invalid, \'to\' cannot come before \'from\'.'
|
|
elsif from_date > Date.today
|
|
halt 400, 'Date range is invalid, \'from\' must be in the past.'
|
|
end
|
|
|
|
case params[:route]
|
|
when 'boot'
|
|
result = get_task_summary(backend, 'boot', from_date, to_date, :bypool => true, :only => params[:key])
|
|
when 'clone'
|
|
result = get_task_summary(backend, 'clone', from_date, to_date, :bypool => true, :only => params[:key])
|
|
when 'tag'
|
|
result = get_tag_summary(backend, from_date, to_date, :only => params[:key])
|
|
else
|
|
halt 404, JSON.pretty_generate({ 'ok' => false })
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/token/?" do
|
|
content_type :json
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
if Vmpooler::API.settings.config[:auth]
|
|
status 401
|
|
|
|
need_auth!
|
|
|
|
backend.keys('vmpooler__token__*').each do |key|
|
|
data = backend.hgetall(key)
|
|
|
|
if data['user'] == Rack::Auth::Basic::Request.new(request.env).username
|
|
token = key.split('__').last
|
|
|
|
result[token] ||= {}
|
|
|
|
result[token]['created'] = data['created']
|
|
result[token]['last'] = data['last'] || 'never'
|
|
|
|
result['ok'] = true
|
|
end
|
|
end
|
|
|
|
if result['ok']
|
|
status 200
|
|
else
|
|
status 404
|
|
end
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/token/:token/?" do
|
|
content_type :json
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
if Vmpooler::API.settings.config[:auth]
|
|
token = backend.hgetall("vmpooler__token__#{params[:token]}")
|
|
|
|
if not token.nil? and not token.empty?
|
|
status 200
|
|
|
|
pools.each do |pool|
|
|
backend.smembers("vmpooler__running__#{pool['name']}").each do |vm|
|
|
if backend.hget("vmpooler__vm__#{vm}", 'token:token') == params[:token]
|
|
token['vms'] ||= {}
|
|
token['vms']['running'] ||= []
|
|
token['vms']['running'].push(vm)
|
|
end
|
|
end
|
|
end
|
|
|
|
result = { 'ok' => true, params[:token] => token }
|
|
end
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
delete "#{api_prefix}/token/:token/?" do
|
|
content_type :json
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
if Vmpooler::API.settings.config[:auth]
|
|
status 401
|
|
|
|
need_auth!
|
|
|
|
if backend.del("vmpooler__token__#{params[:token]}").to_i > 0
|
|
status 200
|
|
result['ok'] = true
|
|
end
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/token" do
|
|
content_type :json
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
if Vmpooler::API.settings.config[:auth]
|
|
status 401
|
|
|
|
need_auth!
|
|
|
|
o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten
|
|
result['token'] = o[rand(25)] + (0...31).map { o[rand(o.length)] }.join
|
|
|
|
backend.hset("vmpooler__token__#{result['token']}", 'user', @auth.username)
|
|
backend.hset("vmpooler__token__#{result['token']}", 'created', Time.now)
|
|
|
|
status 200
|
|
result['ok'] = true
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/vm/?" do
|
|
content_type :json
|
|
|
|
result = []
|
|
|
|
pools.each do |pool|
|
|
result.push(pool['name'])
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/ondemandvm/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.post.ondemand.requestid')
|
|
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
result = { 'ok' => false }
|
|
|
|
begin
|
|
payload = JSON.parse(request.body.read)
|
|
|
|
if payload
|
|
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
|
if invalid.empty?
|
|
result = generate_ondemand_request(payload)
|
|
else
|
|
result[:bad_templates] = invalid
|
|
invalid.each do |bad_template|
|
|
metrics.increment("ondemandrequest_fail.invalid.#{bad_template}")
|
|
end
|
|
status 404
|
|
end
|
|
else
|
|
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
|
status 404
|
|
end
|
|
rescue JSON::ParserError
|
|
status 400
|
|
result = {
|
|
'ok' => false,
|
|
'message' => 'JSON payload could not be parsed'
|
|
}
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/ondemandvm/:template/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
metrics.increment('http_requests_vm_total.delete.ondemand.template')
|
|
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
payload = extract_templates_from_query_params(params[:template])
|
|
|
|
if payload
|
|
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
|
if invalid.empty?
|
|
result = generate_ondemand_request(payload)
|
|
else
|
|
result[:bad_templates] = invalid
|
|
invalid.each do |bad_template|
|
|
metrics.increment("ondemandrequest_fail.invalid.#{bad_template}")
|
|
end
|
|
status 404
|
|
end
|
|
else
|
|
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
|
status 404
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/ondemandvm/:requestid/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.get.ondemand.request')
|
|
|
|
status 404
|
|
result = check_ondemand_request(params[:requestid])
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
delete "#{api_prefix}/ondemandvm/:requestid/?" do
|
|
content_type :json
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
metrics.increment('http_requests_vm_total.delete.ondemand.request')
|
|
|
|
status 404
|
|
result = delete_ondemand_request(params[:requestid])
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/vm/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
metrics.increment('http_requests_vm_total.post.vm.checkout')
|
|
|
|
payload = JSON.parse(request.body.read)
|
|
|
|
if payload
|
|
invalid = invalid_templates(payload)
|
|
if invalid.empty?
|
|
result = atomically_allocate_vms(payload)
|
|
else
|
|
invalid.each do |bad_template|
|
|
metrics.increment("checkout.invalid.#{bad_template}")
|
|
end
|
|
status 404
|
|
end
|
|
else
|
|
metrics.increment('checkout.invalid.unknown')
|
|
status 404
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
def extract_templates_from_query_params(params)
|
|
payload = {}
|
|
|
|
params.split('+').each do |template|
|
|
payload[template] ||= 0
|
|
payload[template] += 1
|
|
end
|
|
|
|
payload
|
|
end
|
|
|
|
def invalid_templates(payload)
|
|
invalid = []
|
|
payload.keys.each do |template|
|
|
invalid << template unless pool_exists?(template)
|
|
end
|
|
invalid
|
|
end
|
|
|
|
def invalid_template_or_size(payload)
|
|
invalid = []
|
|
payload.each do |pool, size|
|
|
invalid << pool unless pool_exists?(pool)
|
|
unless is_integer?(size)
|
|
invalid << pool
|
|
next
|
|
end
|
|
invalid << pool unless Integer(size) >= 0
|
|
end
|
|
invalid
|
|
end
|
|
|
|
def invalid_template_or_path(payload)
|
|
invalid = []
|
|
payload.each do |pool, template|
|
|
invalid << pool unless pool_exists?(pool)
|
|
invalid << pool unless template.include? '/'
|
|
invalid << pool if template[0] == '/'
|
|
invalid << pool if template[-1] == '/'
|
|
end
|
|
invalid
|
|
end
|
|
|
|
def invalid_pool(payload)
|
|
invalid = []
|
|
payload.each do |pool, _clone_target|
|
|
invalid << pool unless pool_exists?(pool)
|
|
end
|
|
invalid
|
|
end
|
|
|
|
def check_ondemand_request(request_id)
|
|
result = { 'ok' => false }
|
|
request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
|
|
if request_hash.empty?
|
|
result['message'] = "no request found for request_id '#{request_id}'"
|
|
return result
|
|
end
|
|
|
|
result['request_id'] = request_id
|
|
result['ready'] = false
|
|
result['ok'] = true
|
|
status 202
|
|
|
|
case request_hash['status']
|
|
when 'ready'
|
|
result['ready'] = true
|
|
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
|
|
instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
|
|
|
if result.key?(platform_alias)
|
|
result[platform_alias][:hostname] = result[platform_alias][:hostname] + instances
|
|
else
|
|
result[platform_alias] = { 'hostname': instances }
|
|
end
|
|
end
|
|
result['domain'] = config['domain'] if config['domain']
|
|
status 200
|
|
when 'failed'
|
|
result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
|
|
status 200
|
|
when 'deleted'
|
|
result['message'] = 'The request has been deleted'
|
|
status 200
|
|
else
|
|
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
|
|
instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
|
instances_pending = count.to_i - instance_count.to_i
|
|
|
|
if result.key?(platform_alias) && result[platform_alias].key?(:ready)
|
|
result[platform_alias][:ready] = (result[platform_alias][:ready].to_i + instance_count).to_s
|
|
result[platform_alias][:pending] = (result[platform_alias][:pending].to_i + instances_pending).to_s
|
|
else
|
|
result[platform_alias] = {
|
|
'ready': instance_count.to_s,
|
|
'pending': instances_pending.to_s
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def delete_ondemand_request(request_id)
|
|
result = { 'ok' => false }
|
|
|
|
platforms = backend.hget("vmpooler__odrequest__#{request_id}", 'requested')
|
|
unless platforms
|
|
result['message'] = "no request found for request_id '#{request_id}'"
|
|
return result
|
|
end
|
|
|
|
if backend.hget("vmpooler__odrequest__#{request_id}", 'status') == 'deleted'
|
|
result['message'] = 'the request has already been deleted'
|
|
else
|
|
backend.hset("vmpooler__odrequest__#{request_id}", 'status', 'deleted')
|
|
|
|
Parsing.get_platform_pool_count(platforms) do |platform_alias, pool, _count|
|
|
backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")&.each do |vm|
|
|
backend.smove("vmpooler__running__#{pool}", "vmpooler__completed__#{pool}", vm)
|
|
end
|
|
backend.del("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
|
end
|
|
backend.expire("vmpooler__odrequest__#{request_id}", 129_600_0)
|
|
end
|
|
status 200
|
|
result['ok'] = true
|
|
result
|
|
end
|
|
|
|
post "#{api_prefix}/vm/:template/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
metrics.increment('http_requests_vm_total.get.vm.template')
|
|
|
|
payload = extract_templates_from_query_params(params[:template])
|
|
|
|
if payload
|
|
invalid = invalid_templates(payload)
|
|
if invalid.empty?
|
|
result = atomically_allocate_vms(payload)
|
|
else
|
|
invalid.each do |bad_template|
|
|
metrics.increment("checkout.invalid.#{bad_template}")
|
|
end
|
|
status 404
|
|
end
|
|
else
|
|
metrics.increment('checkout.invalid.unknown')
|
|
status 404
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/vm/:hostname/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.get.vm.hostname')
|
|
|
|
result = {}
|
|
|
|
status 404
|
|
result['ok'] = false
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
rdata = backend.hgetall("vmpooler__vm__#{params[:hostname]}")
|
|
unless rdata.empty?
|
|
status 200
|
|
result['ok'] = true
|
|
|
|
result[params[:hostname]] = {}
|
|
|
|
result[params[:hostname]]['template'] = rdata['template']
|
|
result[params[:hostname]]['lifetime'] = (rdata['lifetime'] || config['vm_lifetime']).to_i
|
|
|
|
if rdata['destroy']
|
|
result[params[:hostname]]['running'] = ((Time.parse(rdata['destroy']) - Time.parse(rdata['checkout'])) / 60 / 60).round(2) if rdata['checkout']
|
|
result[params[:hostname]]['state'] = 'destroyed'
|
|
elsif rdata['checkout']
|
|
result[params[:hostname]]['running'] = ((Time.now - Time.parse(rdata['checkout'])) / 60 / 60).round(2)
|
|
result[params[:hostname]]['remaining'] = ((Time.parse(rdata['checkout']) + rdata['lifetime'].to_i*60*60 - Time.now) / 60 / 60).round(2)
|
|
result[params[:hostname]]['start_time'] = Time.parse(rdata['checkout']).to_datetime.rfc3339
|
|
result[params[:hostname]]['end_time'] = (Time.parse(rdata['checkout']) + rdata['lifetime'].to_i*60*60).to_datetime.rfc3339
|
|
result[params[:hostname]]['state'] = 'running'
|
|
elsif rdata['check']
|
|
result[params[:hostname]]['state'] = 'ready'
|
|
else
|
|
result[params[:hostname]]['state'] = 'pending'
|
|
end
|
|
|
|
rdata.keys.each do |key|
|
|
if key.match('^tag\:(.+?)$')
|
|
result[params[:hostname]]['tags'] ||= {}
|
|
result[params[:hostname]]['tags'][$1] = rdata[key]
|
|
end
|
|
|
|
if key.match('^snapshot\:(.+?)$')
|
|
result[params[:hostname]]['snapshots'] ||= []
|
|
result[params[:hostname]]['snapshots'].push($1)
|
|
end
|
|
end
|
|
|
|
if rdata['disk']
|
|
result[params[:hostname]]['disk'] = rdata['disk'].split(':')
|
|
end
|
|
|
|
# Look up IP address of the hostname
|
|
begin
|
|
ipAddress = TCPSocket.gethostbyname(params[:hostname])[3]
|
|
rescue StandardError
|
|
ipAddress = ""
|
|
end
|
|
|
|
result[params[:hostname]]['ip'] = ipAddress
|
|
|
|
if config['domain']
|
|
result[params[:hostname]]['domain'] = config['domain']
|
|
end
|
|
|
|
result[params[:hostname]]['host'] = rdata['host'] if rdata['host']
|
|
result[params[:hostname]]['migrated'] = rdata['migrated'] if rdata['migrated']
|
|
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
delete "#{api_prefix}/vm/:hostname/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.delete.vm.hostname')
|
|
|
|
result = {}
|
|
|
|
status 404
|
|
result['ok'] = false
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
rdata = backend.hgetall("vmpooler__vm__#{params[:hostname]}")
|
|
unless rdata.empty?
|
|
need_token! if rdata['token:token']
|
|
|
|
if backend.srem("vmpooler__running__#{rdata['template']}", params[:hostname])
|
|
backend.sadd("vmpooler__completed__#{rdata['template']}", params[:hostname])
|
|
|
|
status 200
|
|
result['ok'] = true
|
|
metrics.increment('delete.success')
|
|
update_user_metrics('destroy', params[:hostname]) if Vmpooler::API.settings.config[:config]['usage_stats']
|
|
else
|
|
metrics.increment('delete.failed')
|
|
end
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
put "#{api_prefix}/vm/:hostname/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.put.vm.modify')
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
failure = []
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
if backend.exists?("vmpooler__vm__#{params[:hostname]}")
|
|
begin
|
|
jdata = JSON.parse(request.body.read)
|
|
rescue StandardError
|
|
halt 400, JSON.pretty_generate(result)
|
|
end
|
|
|
|
# Validate data payload
|
|
jdata.each do |param, arg|
|
|
case param
|
|
when 'lifetime'
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
# in hours, defaults to one week
|
|
max_lifetime_upper_limit = config['max_lifetime_upper_limit']
|
|
if max_lifetime_upper_limit
|
|
max_lifetime_upper_limit = max_lifetime_upper_limit.to_i
|
|
if arg.to_i >= max_lifetime_upper_limit
|
|
failure.push("You provided a lifetime (#{arg}) that exceeds the configured maximum of #{max_lifetime_upper_limit}.")
|
|
end
|
|
end
|
|
|
|
# validate lifetime is within boundaries
|
|
unless arg.to_i > 0
|
|
failure.push("You provided a lifetime (#{arg}) but you must provide a positive number.")
|
|
end
|
|
|
|
when 'tags'
|
|
failure.push("You provided tags (#{arg}) as something other than a hash.") unless arg.is_a?(Hash)
|
|
failure.push("You provided unsuppored tags (#{arg}).") if config['allowed_tags'] && !(arg.keys - config['allowed_tags']).empty?
|
|
else
|
|
failure.push("Unknown argument #{arg}.")
|
|
end
|
|
end
|
|
|
|
if !failure.empty?
|
|
status 400
|
|
result['failure'] = failure
|
|
else
|
|
jdata.each do |param, arg|
|
|
case param
|
|
when 'lifetime'
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
arg = arg.to_i
|
|
|
|
backend.hset("vmpooler__vm__#{params[:hostname]}", param, arg)
|
|
when 'tags'
|
|
filter_tags(arg)
|
|
export_tags(backend, params[:hostname], arg)
|
|
end
|
|
end
|
|
|
|
status 200
|
|
result['ok'] = true
|
|
end
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/vm/:hostname/disk/:size/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.post.vm.disksize')
|
|
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
if ((params[:size].to_i > 0 )and (backend.exists?("vmpooler__vm__#{params[:hostname]}")))
|
|
result[params[:hostname]] = {}
|
|
result[params[:hostname]]['disk'] = "+#{params[:size]}gb"
|
|
|
|
backend.sadd('vmpooler__tasks__disk', "#{params[:hostname]}:#{params[:size]}")
|
|
|
|
status 202
|
|
result['ok'] = true
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/vm/:hostname/snapshot/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.post.vm.snapshot')
|
|
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
if backend.exists?("vmpooler__vm__#{params[:hostname]}")
|
|
result[params[:hostname]] = {}
|
|
|
|
o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten
|
|
result[params[:hostname]]['snapshot'] = o[rand(25)] + (0...31).map { o[rand(o.length)] }.join
|
|
|
|
backend.sadd('vmpooler__tasks__snapshot', "#{params[:hostname]}:#{result[params[:hostname]]['snapshot']}")
|
|
|
|
status 202
|
|
result['ok'] = true
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/vm/:hostname/snapshot/:snapshot/?" do
|
|
content_type :json
|
|
metrics.increment('http_requests_vm_total.post.vm.disksize')
|
|
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
status 404
|
|
result = { 'ok' => false }
|
|
|
|
params[:hostname] = hostname_shorten(params[:hostname], config['domain'])
|
|
|
|
unless backend.hget("vmpooler__vm__#{params[:hostname]}", "snapshot:#{params[:snapshot]}").to_i.zero?
|
|
backend.sadd('vmpooler__tasks__snapshot-revert', "#{params[:hostname]}:#{params[:snapshot]}")
|
|
|
|
status 202
|
|
result['ok'] = true
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/config/poolsize/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
|
|
if config['experimental_features']
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
payload = JSON.parse(request.body.read)
|
|
|
|
if payload
|
|
invalid = invalid_template_or_size(payload)
|
|
if invalid.empty?
|
|
result = update_pool_size(payload)
|
|
else
|
|
invalid.each do |bad_template|
|
|
metrics.increment("config.invalid.#{bad_template}")
|
|
end
|
|
result[:not_configured] = invalid
|
|
status 400
|
|
end
|
|
else
|
|
metrics.increment('config.invalid.unknown')
|
|
status 404
|
|
end
|
|
else
|
|
status 405
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/config/pooltemplate/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
|
|
if config['experimental_features']
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
payload = JSON.parse(request.body.read)
|
|
|
|
if payload
|
|
invalid = invalid_template_or_path(payload)
|
|
if invalid.empty?
|
|
result = update_pool_template(payload)
|
|
else
|
|
invalid.each do |bad_template|
|
|
metrics.increment("config.invalid.#{bad_template}")
|
|
end
|
|
result[:bad_templates] = invalid
|
|
status 400
|
|
end
|
|
else
|
|
metrics.increment('config.invalid.unknown')
|
|
status 404
|
|
end
|
|
else
|
|
status 405
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/poolreset/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
|
|
if config['experimental_features']
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
begin
|
|
payload = JSON.parse(request.body.read)
|
|
if payload
|
|
invalid = invalid_templates(payload)
|
|
if invalid.empty?
|
|
result = reset_pool(payload)
|
|
else
|
|
invalid.each do |bad_pool|
|
|
metrics.increment("poolreset.invalid.#{bad_pool}")
|
|
end
|
|
result[:bad_pools] = invalid
|
|
status 400
|
|
end
|
|
else
|
|
metrics.increment('poolreset.invalid.unknown')
|
|
status 404
|
|
end
|
|
rescue JSON::ParserError
|
|
status 400
|
|
result = {
|
|
'ok' => false,
|
|
'message' => 'JSON payload could not be parsed'
|
|
}
|
|
end
|
|
else
|
|
status 405
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
post "#{api_prefix}/config/clonetarget/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
|
|
if config['experimental_features']
|
|
need_token! if Vmpooler::API.settings.config[:auth]
|
|
|
|
payload = JSON.parse(request.body.read)
|
|
|
|
if payload
|
|
invalid = invalid_pool(payload)
|
|
if invalid.empty?
|
|
result = update_clone_target(payload)
|
|
else
|
|
invalid.each do |bad_template|
|
|
metrics.increment("config.invalid.#{bad_template}")
|
|
end
|
|
result[:bad_templates] = invalid
|
|
status 400
|
|
end
|
|
else
|
|
metrics.increment('config.invalid.unknown')
|
|
status 404
|
|
end
|
|
else
|
|
status 405
|
|
end
|
|
|
|
JSON.pretty_generate(result)
|
|
end
|
|
|
|
get "#{api_prefix}/config/?" do
|
|
content_type :json
|
|
result = { 'ok' => false }
|
|
status 404
|
|
|
|
if pools
|
|
sync_pool_sizes
|
|
sync_pool_templates
|
|
|
|
pool_configuration = []
|
|
pools.each do |pool|
|
|
pool['template_ready'] = template_ready?(pool, backend)
|
|
pool_configuration << pool
|
|
end
|
|
|
|
result = {
|
|
pool_configuration: pool_configuration,
|
|
status: {
|
|
ok: true
|
|
}
|
|
}
|
|
|
|
status 200
|
|
end
|
|
JSON.pretty_generate(result)
|
|
end
|
|
end
|
|
end
|
|
end
|