-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[QENG-3919] Make vmpooler checkouts be all or nothing #153
Changes from 18 commits
e323a7f
d3c5d3b
2cd83af
7a587b8
93e218c
c0cf772
0e66d35
67748e3
2ea47a9
2f9bc40
5032b51
07ca820
8943ff4
c4cac5b
06ae95c
61f57d5
3e6938c
9123cf7
9c1add1
f2c9720
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,38 +45,75 @@ def alias_deref(hash) | |
newhash | ||
end | ||
|
||
def checkout_vm(template, result) | ||
vm = backend.spop('vmpooler__ready__' + template) | ||
def fetch_single_vm(template) | ||
backend.spop('vmpooler__ready__' + template) | ||
end | ||
|
||
def return_single_vm(template, vm) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This confused me at first - could we rename it to something like re_ready_single_vm or something along those lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure thing! |
||
backend.sadd('vmpooler__ready__' + template, vm) | ||
end | ||
|
||
unless vm.nil? | ||
backend.sadd('vmpooler__running__' + template, vm) | ||
backend.hset('vmpooler__active__' + template, vm, Time.now) | ||
backend.hset('vmpooler__vm__' + vm, 'checkout', Time.now) | ||
def account_for_starting_vm(template, vm) | ||
backend.sadd('vmpooler__running__' + 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? | ||
validate_token(backend) | ||
if Vmpooler::API.settings.config[:auth] and has_token? | ||
validate_token(backend) | ||
|
||
backend.hset('vmpooler__vm__' + vm, 'token:token', request.env['HTTP_X_AUTH_TOKEN']) | ||
backend.hset('vmpooler__vm__' + vm, 'token:user', | ||
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user') | ||
) | ||
backend.hset('vmpooler__vm__' + vm, 'token:token', request.env['HTTP_X_AUTH_TOKEN']) | ||
backend.hset('vmpooler__vm__' + vm, 'token:user', | ||
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user') | ||
) | ||
|
||
if config['vm_lifetime_auth'].to_i > 0 | ||
backend.hset('vmpooler__vm__' + vm, 'lifetime', config['vm_lifetime_auth'].to_i) | ||
end | ||
if config['vm_lifetime_auth'].to_i > 0 | ||
backend.hset('vmpooler__vm__' + vm, 'lifetime', config['vm_lifetime_auth'].to_i) | ||
end | ||
end | ||
end | ||
|
||
result[template] ||= {} | ||
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 | ||
|
||
if result[template]['hostname'] | ||
result[template]['hostname'] = [result[template]['hostname']] unless result[template]['hostname'].is_a?(Array) | ||
result[template]['hostname'].push(vm) | ||
else | ||
result[template]['hostname'] = vm | ||
def atomically_allocate_vms(payload) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case of single-vm requests we "checkout vm" but in the case of multiple-vm requests we "atomically allocate vms"...should we try to align the semantics between these two methods to make it clear that they serve similar purposes? (the former being an operation that returns a scalar whereas the latter returns a collection) At the very least it seems like we should be able to say that the scalar operation is trivially atomic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll see if there's a straightforward way to unify these two code paths. There are definitely a couple/few places where there are array vs. scalar semantics that are a bit ugly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out, |
||
return false unless payload and !payload.empty? | ||
|
||
result = { 'ok' => false } | ||
failed = false | ||
vms = [] | ||
|
||
payload.each do |template, count| | ||
count.to_i.times do |_i| | ||
vm = fetch_single_vm(template) | ||
if !vm | ||
failed = true | ||
break | ||
else | ||
vms << [ template, vm ] | ||
end | ||
end | ||
end | ||
|
||
if failed | ||
vms.each do |(template, vm)| | ||
return_single_vm(template, vm) | ||
status 503 | ||
end | ||
else | ||
status 503 | ||
result['ok'] = false | ||
vms.each do |(template, vm)| | ||
account_for_starting_vm(template, vm) | ||
update_result_hosts(result, template, vm) | ||
end | ||
|
||
result['ok'] = true | ||
result['domain'] = config['domain'] if config['domain'] | ||
end | ||
|
||
result | ||
|
@@ -334,80 +371,41 @@ def checkout_vm(template, result) | |
end | ||
|
||
post "#{api_prefix}/vm/?" do | ||
jdata = alias_deref(JSON.parse(request.body.read)) | ||
content_type :json | ||
|
||
result = { 'ok' => false } | ||
|
||
jdata = alias_deref(JSON.parse(request.body.read)) | ||
|
||
if not jdata.nil? and not jdata.empty? | ||
available = 1 | ||
if jdata and !jdata.empty? | ||
result = atomically_allocate_vms(jdata) | ||
else | ||
status 404 | ||
end | ||
|
||
jdata.each do |key, val| | ||
if backend.scard('vmpooler__ready__' + key).to_i < val.to_i | ||
available = 0 | ||
end | ||
end | ||
|
||
if (available == 1) | ||
result['ok'] = true | ||
JSON.pretty_generate(result) | ||
end | ||
|
||
jdata.each do |key, val| | ||
val.to_i.times do |_i| | ||
result = checkout_vm(key, result) | ||
end | ||
end | ||
end | ||
def extract_templates_from_query_params(params) | ||
payload = {} | ||
|
||
if result['ok'] && config['domain'] | ||
result['domain'] = config['domain'] | ||
params.split('+').each do |template| | ||
payload[template] ||= 0 | ||
payload[template] += 1 | ||
end | ||
|
||
JSON.pretty_generate(result) | ||
payload | ||
end | ||
|
||
post "#{api_prefix}/vm/:template/?" do | ||
payload = alias_deref(extract_templates_from_query_params(params[:template])) | ||
content_type :json | ||
|
||
result = { 'ok' => false } | ||
payload = {} | ||
|
||
params[:template].split('+').each do |template| | ||
payload[template] ||= 0 | ||
payload[template] = payload[template] + 1 | ||
end | ||
|
||
payload = alias_deref(payload) | ||
|
||
if not payload.nil? and not payload.empty? | ||
available = 1 | ||
if payload and !payload.empty? | ||
result = atomically_allocate_vms(payload) | ||
else | ||
status 404 | ||
end | ||
|
||
payload.each do |key, val| | ||
if backend.scard('vmpooler__ready__' + key).to_i < val.to_i | ||
available = 0 | ||
end | ||
end | ||
|
||
if (available == 1) | ||
result['ok'] = true | ||
|
||
payload.each do |key, val| | ||
val.to_i.times do |_i| | ||
result = checkout_vm(key, result) | ||
end | ||
end | ||
end | ||
|
||
if result['ok'] && config['domain'] | ||
result['domain'] = config['domain'] | ||
end | ||
|
||
JSON.pretty_generate(result) | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @shermdog (hard-wired to make travis happy on jruby, etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not hardwire as we do have some customers using this tool.