vmpooler 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/vmpooler/api/helpers.rb +11 -9
- data/lib/vmpooler/api/reroute.rb +16 -0
- data/lib/vmpooler/api/v1.rb +18 -0
- data/lib/vmpooler/api/v2.rb +429 -0
- data/lib/vmpooler/api.rb +2 -1
- data/lib/vmpooler/pool_manager.rb +70 -55
- data/lib/vmpooler/util/parsing.rb +21 -1
- data/lib/vmpooler/version.rb +1 -1
- data/lib/vmpooler.rb +4 -0
- metadata +51 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b65cd0483ea44e3ad8642c8c6ed8e22825601c5e707f7e277795eebb76057dd1
|
4
|
+
data.tar.gz: 1244464e0d901dac2878364dcd37a0e5c67ec19ae7736064751889913e8997c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbc749d93c4da92e0009ccd8e792067b088fa024209355d7e9a71a8357dd91de05be6c3b62704adcace89da7b99328a9b282edc1be8b2e6753dc69f709625db9
|
7
|
+
data.tar.gz: d7c8071069919512beb841dd3ad453f0e12020565464fc62f24beff4353cffe9c997d2b0a805dde6eddc42fe00a7784eec82a319a8b7954e56f683a1bf856c05
|
data/lib/vmpooler/api/helpers.rb
CHANGED
@@ -147,12 +147,12 @@ module Vmpooler
|
|
147
147
|
|
148
148
|
def export_tags(backend, hostname, tags)
|
149
149
|
tracer.in_span("Vmpooler::API::Helpers.#{__method__}") do
|
150
|
-
backend.pipelined do
|
150
|
+
backend.pipelined do |pipeline|
|
151
151
|
tags.each_pair do |tag, value|
|
152
152
|
next if value.nil? or value.empty?
|
153
153
|
|
154
|
-
|
155
|
-
|
154
|
+
pipeline.hset("vmpooler__vm__#{hostname}", "tag:#{tag}", value)
|
155
|
+
pipeline.hset("vmpooler__tag__#{Date.today}", "#{hostname}:#{tag}", value)
|
156
156
|
end
|
157
157
|
end
|
158
158
|
end
|
@@ -184,6 +184,8 @@ module Vmpooler
|
|
184
184
|
def hostname_shorten(hostname, domain=nil)
|
185
185
|
if domain && hostname =~ /^[\w-]+\.#{domain}$/
|
186
186
|
hostname = hostname[/[^.]+/]
|
187
|
+
elsif hostname =~ /^[\w-]+\..+$/
|
188
|
+
hostname = hostname[/[^.]+/]
|
187
189
|
end
|
188
190
|
|
189
191
|
hostname
|
@@ -201,9 +203,9 @@ module Vmpooler
|
|
201
203
|
tracer.in_span("Vmpooler::API::Helpers.#{__method__}") do
|
202
204
|
# using pipelined is much faster than querying each of the pools and adding them
|
203
205
|
# as we get the result.
|
204
|
-
res = backend.pipelined do
|
206
|
+
res = backend.pipelined do |pipeline|
|
205
207
|
pools.each do |pool|
|
206
|
-
|
208
|
+
pipeline.scard(key + pool['name'])
|
207
209
|
end
|
208
210
|
end
|
209
211
|
res.inject(0) { |m, x| m + x }.to_i
|
@@ -217,9 +219,9 @@ module Vmpooler
|
|
217
219
|
# using pipelined is much faster than querying each of the pools and adding them
|
218
220
|
# as we get the result.
|
219
221
|
temp_hash = {}
|
220
|
-
res = backend.pipelined do
|
222
|
+
res = backend.pipelined do |pipeline|
|
221
223
|
pools.each do |pool|
|
222
|
-
|
224
|
+
pipeline.scard(key + pool['name'])
|
223
225
|
end
|
224
226
|
end
|
225
227
|
pools.each_with_index do |pool, i|
|
@@ -236,9 +238,9 @@ module Vmpooler
|
|
236
238
|
# using pipelined is much faster than querying each of the pools and adding them
|
237
239
|
# as we get the result.
|
238
240
|
temp_hash = {}
|
239
|
-
res = backend.pipelined do
|
241
|
+
res = backend.pipelined do |pipeline|
|
240
242
|
pools.each do |pool|
|
241
|
-
|
243
|
+
pipeline.hget(key, pool['name'])
|
242
244
|
end
|
243
245
|
end
|
244
246
|
pools.each_with_index do |pool, i|
|
data/lib/vmpooler/api/reroute.rb
CHANGED
@@ -6,66 +6,82 @@ module Vmpooler
|
|
6
6
|
api_version = '1'
|
7
7
|
|
8
8
|
get '/status/?' do
|
9
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /status/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
9
10
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/status")
|
10
11
|
end
|
11
12
|
|
12
13
|
get '/summary/?' do
|
14
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /summary/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
13
15
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/summary")
|
14
16
|
end
|
15
17
|
|
16
18
|
get '/summary/:route/?:key?/?' do
|
19
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /summary/:route/?:key?/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
17
20
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/summary/#{params[:route]}/#{params[:key]}")
|
18
21
|
end
|
19
22
|
|
20
23
|
get '/token/?' do
|
24
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /token/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
21
25
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/token")
|
22
26
|
end
|
23
27
|
|
24
28
|
post '/token/?' do
|
29
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called post /token/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
25
30
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/token")
|
26
31
|
end
|
27
32
|
|
28
33
|
get '/token/:token/?' do
|
34
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /token/:token/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
29
35
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/token/#{params[:token]}")
|
30
36
|
end
|
31
37
|
|
32
38
|
delete '/token/:token/?' do
|
39
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called delete /token/:token/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
33
40
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/token/#{params[:token]}")
|
34
41
|
end
|
35
42
|
|
36
43
|
get '/vm/?' do
|
44
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /vm? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
37
45
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm")
|
38
46
|
end
|
39
47
|
|
40
48
|
post '/vm/?' do
|
49
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called post /vm? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
41
50
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm")
|
42
51
|
end
|
43
52
|
|
44
53
|
post '/vm/:template/?' do
|
54
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called post /vm/:template/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
45
55
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:template]}")
|
46
56
|
end
|
47
57
|
|
48
58
|
get '/vm/:hostname/?' do
|
59
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called /vm/:hostname/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
49
60
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}")
|
50
61
|
end
|
51
62
|
|
52
63
|
delete '/vm/:hostname/?' do
|
64
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called delete /vm/:hostname/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
53
65
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}")
|
54
66
|
end
|
55
67
|
|
56
68
|
put '/vm/:hostname/?' do
|
69
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called put /vm/:hostname/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
57
70
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}")
|
58
71
|
end
|
59
72
|
|
60
73
|
post '/vm/:hostname/snapshot/?' do
|
74
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called post /vm/:hostname/snapshot/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
61
75
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}/snapshot")
|
62
76
|
end
|
63
77
|
|
64
78
|
post '/vm/:hostname/snapshot/:snapshot/?' do
|
79
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called post /vm/:hostname/snapshot/:snapshot/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
65
80
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}/snapshot/#{params[:snapshot]}")
|
66
81
|
end
|
67
82
|
|
68
83
|
put '/vm/:hostname/disk/:size/?' do
|
84
|
+
puts "DEPRECATION WARNING a client (#{request.user_agent}) called put /vm/:hostname/disk/:size/? and got redirected to api_version=1, this behavior will change in the next major version, please modify the client to use v2 in advance"
|
69
85
|
call env.merge('PATH_INFO' => "/api/v#{api_version}/vm/#{params[:hostname]}/disk/#{params[:size]}")
|
70
86
|
end
|
71
87
|
end
|
data/lib/vmpooler/api/v1.rb
CHANGED
@@ -24,6 +24,10 @@ module Vmpooler
|
|
24
24
|
Vmpooler::API.settings.config[:config]
|
25
25
|
end
|
26
26
|
|
27
|
+
def full_config
|
28
|
+
Vmpooler::API.settings.config
|
29
|
+
end
|
30
|
+
|
27
31
|
def pools
|
28
32
|
Vmpooler::API.settings.config[:pools]
|
29
33
|
end
|
@@ -1734,6 +1738,20 @@ module Vmpooler
|
|
1734
1738
|
end
|
1735
1739
|
JSON.pretty_generate(result)
|
1736
1740
|
end
|
1741
|
+
|
1742
|
+
get "#{api_prefix}/full_config/?" do
|
1743
|
+
content_type :json
|
1744
|
+
|
1745
|
+
result = {
|
1746
|
+
full_config: full_config,
|
1747
|
+
status: {
|
1748
|
+
ok: true
|
1749
|
+
}
|
1750
|
+
}
|
1751
|
+
|
1752
|
+
status 200
|
1753
|
+
JSON.pretty_generate(result)
|
1754
|
+
end
|
1737
1755
|
end
|
1738
1756
|
end
|
1739
1757
|
end
|
@@ -0,0 +1,429 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'vmpooler/api/v1'
|
4
|
+
|
5
|
+
module Vmpooler
|
6
|
+
class API
|
7
|
+
class V2 < Vmpooler::API::V1
|
8
|
+
api_version = '2'
|
9
|
+
api_prefix = "/api/v#{api_version}"
|
10
|
+
|
11
|
+
def full_config
|
12
|
+
Vmpooler::API.settings.config
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_template_aliases(template)
|
16
|
+
tracer.in_span("Vmpooler::API::V2.#{__method__}") do
|
17
|
+
result = []
|
18
|
+
aliases = Vmpooler::API.settings.config[:alias]
|
19
|
+
if aliases
|
20
|
+
result += aliases[template] if aliases[template].is_a?(Array)
|
21
|
+
template_backends << aliases[template] if aliases[template].is_a?(String)
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Fetch a single vm from a pool
|
28
|
+
#
|
29
|
+
# @param [String] template
|
30
|
+
# The template that the vm should be created from
|
31
|
+
#
|
32
|
+
# @return [Tuple] vmname, vmpool, vmtemplate
|
33
|
+
# Returns a tuple containing the vm's name, the pool it came from, and
|
34
|
+
# what template was used, if successful. Otherwise the tuple contains.
|
35
|
+
# nil values.
|
36
|
+
def fetch_single_vm(template)
|
37
|
+
tracer.in_span("Vmpooler::API::V2.#{__method__}") do
|
38
|
+
template_backends = [template]
|
39
|
+
aliases = Vmpooler::API.settings.config[:alias]
|
40
|
+
if aliases
|
41
|
+
template_backends += aliases[template] if aliases[template].is_a?(Array)
|
42
|
+
template_backends << aliases[template] if aliases[template].is_a?(String)
|
43
|
+
pool_index = pool_index(pools)
|
44
|
+
weighted_pools = {}
|
45
|
+
template_backends.each do |t|
|
46
|
+
next unless pool_index.key? t
|
47
|
+
|
48
|
+
index = pool_index[t]
|
49
|
+
clone_target = pools[index]['clone_target'] || config['clone_target']
|
50
|
+
next unless config.key?('backend_weight')
|
51
|
+
|
52
|
+
weight = config['backend_weight'][clone_target]
|
53
|
+
if weight
|
54
|
+
weighted_pools[t] = weight
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if weighted_pools.count == template_backends.count
|
59
|
+
pickup = Pickup.new(weighted_pools)
|
60
|
+
selection = pickup.pick
|
61
|
+
template_backends.delete(selection)
|
62
|
+
template_backends.unshift(selection)
|
63
|
+
else
|
64
|
+
first = template_backends.sample
|
65
|
+
template_backends.delete(first)
|
66
|
+
template_backends.unshift(first)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
checkoutlock.synchronize do
|
71
|
+
template_backends.each do |template_backend|
|
72
|
+
vms = backend.smembers("vmpooler__ready__#{template_backend}")
|
73
|
+
next if vms.empty?
|
74
|
+
|
75
|
+
vm = vms.pop
|
76
|
+
smoved = backend.smove("vmpooler__ready__#{template_backend}", "vmpooler__running__#{template_backend}", vm)
|
77
|
+
if smoved
|
78
|
+
return [vm, template_backend, template]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
[nil, nil, nil]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# The domain in the result body will be set to the one associated with the
|
87
|
+
# last vm added. The part of the response is only being retained for
|
88
|
+
# backwards compatibility as the hostnames are now fqdn's instead of bare
|
89
|
+
# hostnames. This change is a result of now being able to specify a domain
|
90
|
+
# per pool. If no vm's in the result had a domain sepcified then the
|
91
|
+
# domain key will be omitted similar to how it was previously omitted if
|
92
|
+
# the global option domain wasn't specified.
|
93
|
+
def atomically_allocate_vms(payload)
|
94
|
+
tracer.in_span("Vmpooler::API::V2.#{__method__}") do |span|
|
95
|
+
result = { 'ok' => false }
|
96
|
+
failed = false
|
97
|
+
vms = [] # vmpool, vmname, vmtemplate
|
98
|
+
|
99
|
+
validate_token(backend) if Vmpooler::API.settings.config[:auth] and has_token?
|
100
|
+
|
101
|
+
payload.each do |requested, count|
|
102
|
+
count.to_i.times do |_i|
|
103
|
+
vmname, vmpool, vmtemplate = fetch_single_vm(requested)
|
104
|
+
if vmname
|
105
|
+
account_for_starting_vm(vmpool, vmname)
|
106
|
+
vms << [vmpool, vmname, vmtemplate]
|
107
|
+
metrics.increment("checkout.success.#{vmpool}")
|
108
|
+
update_user_metrics('allocate', vmname) if Vmpooler::API.settings.config[:config]['usage_stats']
|
109
|
+
else
|
110
|
+
failed = true
|
111
|
+
metrics.increment("checkout.empty.#{requested}")
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if failed
|
118
|
+
vms.each do |(vmpool, vmname, _vmtemplate)|
|
119
|
+
return_vm_to_ready_state(vmpool, vmname)
|
120
|
+
end
|
121
|
+
span.add_event('error', attributes: {
|
122
|
+
'error.type' => 'Vmpooler::API::V2.atomically_allocate_vms',
|
123
|
+
'error.message' => '503 due to failing to allocate one or more vms'
|
124
|
+
})
|
125
|
+
status 503
|
126
|
+
else
|
127
|
+
vm_names = []
|
128
|
+
vms.each do |(vmpool, vmname, vmtemplate)|
|
129
|
+
vmdomain = Parsing.get_domain_for_pool(full_config, vmpool)
|
130
|
+
if vmdomain
|
131
|
+
vmfqdn = "#{vmname}.#{vmdomain}"
|
132
|
+
update_result_hosts(result, vmtemplate, vmfqdn)
|
133
|
+
vm_names.append(vmfqdn)
|
134
|
+
else
|
135
|
+
update_result_hosts(result, vmtemplate, vmname)
|
136
|
+
vm_names.append(vmname)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
span.set_attribute('vmpooler.vm_names', vm_names.join(',')) unless vm_names.empty?
|
141
|
+
|
142
|
+
result['ok'] = true
|
143
|
+
end
|
144
|
+
|
145
|
+
result
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def generate_ondemand_request(payload)
|
150
|
+
tracer.in_span("Vmpooler::API::V2.#{__method__}") do |span|
|
151
|
+
result = { 'ok': false }
|
152
|
+
|
153
|
+
requested_instances = payload.reject { |k, _v| k == 'request_id' }
|
154
|
+
if too_many_requested?(requested_instances)
|
155
|
+
e_message = "requested amount of instances exceeds the maximum #{config['max_ondemand_instances_per_request']}"
|
156
|
+
result['message'] = e_message
|
157
|
+
status 403
|
158
|
+
span.add_event('error', attributes: {
|
159
|
+
'error.type' => 'Vmpooler::API::V2.generate_ondemand_request',
|
160
|
+
'error.message' => "403 due to #{e_message}"
|
161
|
+
})
|
162
|
+
return result
|
163
|
+
end
|
164
|
+
|
165
|
+
score = Time.now.to_i
|
166
|
+
request_id = payload['request_id']
|
167
|
+
request_id ||= generate_request_id
|
168
|
+
result['request_id'] = request_id
|
169
|
+
span.set_attribute('vmpooler.request_id', request_id)
|
170
|
+
|
171
|
+
if backend.exists?("vmpooler__odrequest__#{request_id}")
|
172
|
+
e_message = "request_id '#{request_id}' has already been created"
|
173
|
+
result['message'] = e_message
|
174
|
+
status 409
|
175
|
+
span.add_event('error', attributes: {
|
176
|
+
'error.type' => 'Vmpooler::API::V2.generate_ondemand_request',
|
177
|
+
'error.message' => "409 due to #{e_message}"
|
178
|
+
})
|
179
|
+
metrics.increment('ondemandrequest_generate.duplicaterequests')
|
180
|
+
return result
|
181
|
+
end
|
182
|
+
|
183
|
+
status 201
|
184
|
+
|
185
|
+
platforms_with_aliases = []
|
186
|
+
requested_instances.each do |poolname, count|
|
187
|
+
selection = evaluate_template_aliases(poolname, count)
|
188
|
+
selection.map { |selected_pool, selected_pool_count| platforms_with_aliases << "#{poolname}:#{selected_pool}:#{selected_pool_count}" }
|
189
|
+
end
|
190
|
+
platforms_string = platforms_with_aliases.join(',')
|
191
|
+
|
192
|
+
return result unless backend.zadd('vmpooler__provisioning__request', score, request_id)
|
193
|
+
|
194
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'requested', platforms_string)
|
195
|
+
if Vmpooler::API.settings.config[:auth] and has_token?
|
196
|
+
token_token = request.env['HTTP_X_AUTH_TOKEN']
|
197
|
+
token_user = backend.hget("vmpooler__token__#{token_token}", 'user')
|
198
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'token:token', token_token)
|
199
|
+
backend.hset("vmpooler__odrequest__#{request_id}", 'token:user', token_user)
|
200
|
+
span.set_attribute('enduser.id', token_user)
|
201
|
+
end
|
202
|
+
|
203
|
+
result[:ok] = true
|
204
|
+
metrics.increment('ondemandrequest_generate.success')
|
205
|
+
result
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Endpoints that use overridden methods
|
210
|
+
|
211
|
+
post "#{api_prefix}/vm/?" do
|
212
|
+
content_type :json
|
213
|
+
result = { 'ok' => false }
|
214
|
+
metrics.increment('http_requests_vm_total.post.vm.checkout')
|
215
|
+
|
216
|
+
payload = JSON.parse(request.body.read)
|
217
|
+
|
218
|
+
if payload
|
219
|
+
invalid = invalid_templates(payload)
|
220
|
+
if invalid.empty?
|
221
|
+
result = atomically_allocate_vms(payload)
|
222
|
+
else
|
223
|
+
invalid.each do |bad_template|
|
224
|
+
metrics.increment("checkout.invalid.#{bad_template}")
|
225
|
+
end
|
226
|
+
status 404
|
227
|
+
end
|
228
|
+
else
|
229
|
+
metrics.increment('checkout.invalid.unknown')
|
230
|
+
status 404
|
231
|
+
end
|
232
|
+
|
233
|
+
JSON.pretty_generate(result)
|
234
|
+
end
|
235
|
+
|
236
|
+
post "#{api_prefix}/vm/:template/?" do
|
237
|
+
content_type :json
|
238
|
+
result = { 'ok' => false }
|
239
|
+
metrics.increment('http_requests_vm_total.get.vm.template')
|
240
|
+
|
241
|
+
payload = extract_templates_from_query_params(params[:template])
|
242
|
+
|
243
|
+
if payload
|
244
|
+
invalid = invalid_templates(payload)
|
245
|
+
if invalid.empty?
|
246
|
+
result = atomically_allocate_vms(payload)
|
247
|
+
else
|
248
|
+
invalid.each do |bad_template|
|
249
|
+
metrics.increment("checkout.invalid.#{bad_template}")
|
250
|
+
end
|
251
|
+
status 404
|
252
|
+
end
|
253
|
+
else
|
254
|
+
metrics.increment('checkout.invalid.unknown')
|
255
|
+
status 404
|
256
|
+
end
|
257
|
+
|
258
|
+
JSON.pretty_generate(result)
|
259
|
+
end
|
260
|
+
|
261
|
+
post "#{api_prefix}/ondemandvm/?" do
|
262
|
+
content_type :json
|
263
|
+
metrics.increment('http_requests_vm_total.post.ondemand.requestid')
|
264
|
+
|
265
|
+
need_token! if Vmpooler::API.settings.config[:auth]
|
266
|
+
|
267
|
+
result = { 'ok' => false }
|
268
|
+
|
269
|
+
begin
|
270
|
+
payload = JSON.parse(request.body.read)
|
271
|
+
|
272
|
+
if payload
|
273
|
+
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
274
|
+
if invalid.empty?
|
275
|
+
result = generate_ondemand_request(payload)
|
276
|
+
else
|
277
|
+
result[:bad_templates] = invalid
|
278
|
+
invalid.each do |bad_template|
|
279
|
+
metrics.increment("ondemandrequest_fail.invalid.#{bad_template}")
|
280
|
+
end
|
281
|
+
status 404
|
282
|
+
end
|
283
|
+
else
|
284
|
+
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
285
|
+
status 404
|
286
|
+
end
|
287
|
+
rescue JSON::ParserError
|
288
|
+
span = OpenTelemetry::Trace.current_span
|
289
|
+
span.status = OpenTelemetry::Trace::Status.error('JSON payload could not be parsed')
|
290
|
+
status 400
|
291
|
+
result = {
|
292
|
+
'ok' => false,
|
293
|
+
'message' => 'JSON payload could not be parsed'
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
JSON.pretty_generate(result)
|
298
|
+
end
|
299
|
+
|
300
|
+
post "#{api_prefix}/ondemandvm/:template/?" do
|
301
|
+
content_type :json
|
302
|
+
result = { 'ok' => false }
|
303
|
+
metrics.increment('http_requests_vm_total.delete.ondemand.template')
|
304
|
+
|
305
|
+
need_token! if Vmpooler::API.settings.config[:auth]
|
306
|
+
|
307
|
+
payload = extract_templates_from_query_params(params[:template])
|
308
|
+
|
309
|
+
if payload
|
310
|
+
invalid = invalid_templates(payload.reject { |k, _v| k == 'request_id' })
|
311
|
+
if invalid.empty?
|
312
|
+
result = generate_ondemand_request(payload)
|
313
|
+
else
|
314
|
+
result[:bad_templates] = invalid
|
315
|
+
invalid.each do |bad_template|
|
316
|
+
metrics.increment("ondemandrequest_fail.invalid.#{bad_template}")
|
317
|
+
end
|
318
|
+
status 404
|
319
|
+
end
|
320
|
+
else
|
321
|
+
metrics.increment('ondemandrequest_fail.invalid.unknown')
|
322
|
+
status 404
|
323
|
+
end
|
324
|
+
|
325
|
+
JSON.pretty_generate(result)
|
326
|
+
end
|
327
|
+
|
328
|
+
get "#{api_prefix}/ondemandvm/:requestid/?" do
|
329
|
+
content_type :json
|
330
|
+
metrics.increment('http_requests_vm_total.get.ondemand.request')
|
331
|
+
|
332
|
+
status 404
|
333
|
+
result = check_ondemand_request(params[:requestid])
|
334
|
+
|
335
|
+
JSON.pretty_generate(result)
|
336
|
+
end
|
337
|
+
|
338
|
+
def check_ondemand_request(request_id)
|
339
|
+
tracer.in_span("Vmpooler::API::V2.#{__method__}") do |span|
|
340
|
+
span.set_attribute('vmpooler.request_id', request_id)
|
341
|
+
result = { 'ok' => false }
|
342
|
+
request_hash = backend.hgetall("vmpooler__odrequest__#{request_id}")
|
343
|
+
if request_hash.empty?
|
344
|
+
e_message = "no request found for request_id '#{request_id}'"
|
345
|
+
result['message'] = e_message
|
346
|
+
span.add_event('error', attributes: {
|
347
|
+
'error.type' => 'Vmpooler::API::V2.check_ondemand_request',
|
348
|
+
'error.message' => e_message
|
349
|
+
})
|
350
|
+
return result
|
351
|
+
end
|
352
|
+
|
353
|
+
result['request_id'] = request_id
|
354
|
+
result['ready'] = false
|
355
|
+
result['ok'] = true
|
356
|
+
status 202
|
357
|
+
|
358
|
+
case request_hash['status']
|
359
|
+
when 'ready'
|
360
|
+
result['ready'] = true
|
361
|
+
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, _count|
|
362
|
+
instances = backend.smembers("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
363
|
+
domain = Parsing.get_domain_for_pool(full_config, pool)
|
364
|
+
instances.map! { |instance| instance.concat(".#{domain}") } if domain
|
365
|
+
|
366
|
+
if result.key?(platform_alias)
|
367
|
+
result[platform_alias][:hostname] = result[platform_alias][:hostname] + instances
|
368
|
+
else
|
369
|
+
result[platform_alias] = { 'hostname': instances }
|
370
|
+
end
|
371
|
+
end
|
372
|
+
status 200
|
373
|
+
when 'failed'
|
374
|
+
result['message'] = "The request failed to provision instances within the configured ondemand_request_ttl '#{config['ondemand_request_ttl']}'"
|
375
|
+
status 200
|
376
|
+
when 'deleted'
|
377
|
+
result['message'] = 'The request has been deleted'
|
378
|
+
status 200
|
379
|
+
else
|
380
|
+
Parsing.get_platform_pool_count(request_hash['requested']) do |platform_alias, pool, count|
|
381
|
+
instance_count = backend.scard("vmpooler__#{request_id}__#{platform_alias}__#{pool}")
|
382
|
+
instances_pending = count.to_i - instance_count.to_i
|
383
|
+
|
384
|
+
if result.key?(platform_alias) && result[platform_alias].key?(:ready)
|
385
|
+
result[platform_alias][:ready] = (result[platform_alias][:ready].to_i + instance_count).to_s
|
386
|
+
result[platform_alias][:pending] = (result[platform_alias][:pending].to_i + instances_pending).to_s
|
387
|
+
else
|
388
|
+
result[platform_alias] = {
|
389
|
+
'ready': instance_count.to_s,
|
390
|
+
'pending': instances_pending.to_s
|
391
|
+
}
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
result
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# Endpoints that only use bits from the V1 api are called here
|
401
|
+
# Note that traces will be named based on the route used in the V1 api
|
402
|
+
# but the http.url trace attribute will still have the actual requested url in it
|
403
|
+
|
404
|
+
delete "#{api_prefix}/*" do
|
405
|
+
versionless_path_info = request.path_info.delete_prefix("#{api_prefix}/")
|
406
|
+
request.path_info = "/api/v1/#{versionless_path_info}"
|
407
|
+
call env
|
408
|
+
end
|
409
|
+
|
410
|
+
get "#{api_prefix}/*" do
|
411
|
+
versionless_path_info = request.path_info.delete_prefix("#{api_prefix}/")
|
412
|
+
request.path_info = "/api/v1/#{versionless_path_info}"
|
413
|
+
call env
|
414
|
+
end
|
415
|
+
|
416
|
+
post "#{api_prefix}/*" do
|
417
|
+
versionless_path_info = request.path_info.delete_prefix("#{api_prefix}/")
|
418
|
+
request.path_info = "/api/v1/#{versionless_path_info}"
|
419
|
+
call env
|
420
|
+
end
|
421
|
+
|
422
|
+
put "#{api_prefix}/*" do
|
423
|
+
versionless_path_info = request.path_info.delete_prefix("#{api_prefix}/")
|
424
|
+
request.path_info = "/api/v1/#{versionless_path_info}"
|
425
|
+
call env
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
data/lib/vmpooler/api.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Vmpooler
|
4
4
|
class API < Sinatra::Base
|
5
5
|
# Load API components
|
6
|
-
%w[helpers dashboard reroute v1 request_logger healthcheck].each do |lib|
|
6
|
+
%w[helpers dashboard reroute v1 v2 request_logger healthcheck].each do |lib|
|
7
7
|
require "vmpooler/api/#{lib}"
|
8
8
|
end
|
9
9
|
# Load dashboard components
|
@@ -54,6 +54,7 @@ module Vmpooler
|
|
54
54
|
use Vmpooler::API::Dashboard
|
55
55
|
use Vmpooler::API::Reroute
|
56
56
|
use Vmpooler::API::V1
|
57
|
+
use Vmpooler::API::V2
|
57
58
|
end
|
58
59
|
|
59
60
|
# Get thee started O WebServer
|