Skip to content

Commit 6954321

Browse files
authored
Merge pull request #53 from mikkergimenez/add_abs_vm_get
Add abs vm get
2 parents d8dd088 + 1e67715 commit 6954321

13 files changed

+489
-27
lines changed

.rubocop.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ Style/TrailingCommaInArrayLiteral:
1414
Style/TrailingCommaInArguments:
1515
EnforcedStyleForMultiline: comma
1616

17-
Layout/AlignHash:
17+
Layout/HashAlignment:
1818
EnforcedHashRocketStyle: table
19-
Layout/IndentFirstHashElement:
19+
Layout/FirstHashElementIndentation:
2020
EnforcedStyle: consistent
21+
Metrics/ParameterLists:
22+
Enabled: False
2123

2224
Style/StderrPuts:
2325
Enabled: false

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring
6666

6767
### vmfloaty dotfile
6868

69-
If you do not wish to continuely specify various config options with the cli, you can have a dotfile in your home directory for some defaults. For example:
69+
If you do not wish to continually specify various config options with the cli, you can have a dotfile in your home directory for some defaults. For example:
7070

7171
#### Basic configuration
7272

@@ -131,6 +131,11 @@ services:
131131
url: 'https://nspooler.example.net/api/v1'
132132
token: 'nspooler-tokenstring'
133133
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
134+
abs:
135+
url: 'https://abs.example.net/'
136+
token: 'abs-tokenstring'
137+
type: 'abs' # <-- 'type' is necessary for any non-vmpooler service
138+
134139
```
135140

136141
With this configuration, you could list available OS types from nspooler like this:

lib/vmfloaty.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
class Vmfloaty
1818
include Commander::Methods
1919

20-
def run
20+
def run # rubocop:disable Metrics/AbcSize
2121
program :version, Vmfloaty::VERSION
2222
program :description, 'A CLI helper tool for Puppet Labs VM poolers to help you stay afloat'
2323

@@ -33,6 +33,7 @@ def run
3333
c.option '--user STRING', String, 'User to authenticate with'
3434
c.option '--url STRING', String, 'URL of pooler service'
3535
c.option '--token STRING', String, 'Token for pooler service'
36+
c.option '--priority STRING', 'Priority for supported backends(ABS) (High(1), Medium(2), Low(3))'
3637
c.option '--notoken', 'Makes a request without a token'
3738
c.option '--force', 'Forces vmfloaty to get requested vms'
3839
c.option '--json', 'Prints retrieved vms in JSON format'
@@ -84,6 +85,7 @@ def run
8485
c.option '--url STRING', String, 'URL of pooler service'
8586
c.action do |args, options|
8687
verbose = options.verbose || config['verbose']
88+
8789
service = Service.new(options, config)
8890
filter = args[0]
8991

lib/vmfloaty/abs.rb

+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# frozen_string_literal: true
2+
3+
require 'vmfloaty/errors'
4+
require 'vmfloaty/http'
5+
require 'faraday'
6+
require 'json'
7+
8+
class ABS
9+
# List active VMs in ABS
10+
# This is what a job request looks like:
11+
# {
12+
# "state":"filled",
13+
# "last_processed":"2019-10-31 20:59:33 +0000",
14+
# "allocated_resources": [
15+
# {
16+
# "hostname":"h3oyntawjm7xdch.delivery.puppetlabs.net",
17+
# "type":"centos-7.2-tmpfs-x86_64",
18+
# "engine":"vmpooler"}
19+
# ],
20+
# "audit_log":{
21+
# "2019-10-30 20:33:12 +0000":"Allocated h3oyntawjm7xdch.delivery.puppetlabs.net for job 1572467589"
22+
# },
23+
# "request":{
24+
# "resources":{
25+
# "centos-7.2-tmpfs-x86_64":1
26+
# },
27+
# "job": {
28+
# "id":1572467589,
29+
# "tags": {
30+
# "user":"mikker",
31+
# "url_string":"floaty://mikker/1572467589"
32+
# },
33+
# "user":"mikker",
34+
# "time-received":1572467589
35+
# }
36+
# }
37+
# }
38+
#
39+
40+
@active_hostnames = {}
41+
42+
def self.list_active(verbose, url, _token, user)
43+
all_jobs = []
44+
@active_hostnames = {}
45+
46+
get_active_requests(verbose, url, user).each do |req_hash|
47+
all_jobs.push(req_hash['request']['job']['id'])
48+
@active_hostnames[req_hash['request']['job']['id']] = req_hash
49+
end
50+
51+
all_jobs
52+
end
53+
54+
def self.get_active_requests(verbose, url, user)
55+
conn = Http.get_conn(verbose, url)
56+
res = conn.get 'status/queue'
57+
requests = JSON.parse(res.body)
58+
59+
ret_val = []
60+
requests.each do |req|
61+
req_hash = JSON.parse(req)
62+
next unless user == req_hash['request']['job']['user']
63+
64+
ret_val.push(req_hash)
65+
end
66+
67+
ret_val
68+
end
69+
70+
def self.all_job_resources_accounted_for(allocated_resources, hosts)
71+
allocated_host_list = allocated_resources.map { |ar| ar['hostname'] }
72+
(allocated_host_list - hosts).empty?
73+
end
74+
75+
def self.delete(verbose, url, hosts, token, user)
76+
# In ABS terms, this is a "returned" host.
77+
conn = Http.get_conn(verbose, url)
78+
conn.headers['X-AUTH-TOKEN'] = token if token
79+
80+
puts "Trying to delete hosts #{hosts}" if verbose
81+
requests = get_active_requests(verbose, url, user)
82+
83+
jobs_to_delete = []
84+
85+
ret_status = {}
86+
hosts.each do |host|
87+
ret_status[host] = {
88+
'ok' => false,
89+
}
90+
end
91+
92+
requests.each do |req_hash|
93+
next unless req_hash['state'] == 'allocated' || req_hash['state'] == 'filled'
94+
95+
req_hash['allocated_resources'].each do |vm_name, _i|
96+
if hosts.include? vm_name['hostname']
97+
if all_job_resources_accounted_for(req_hash['allocated_resources'], hosts)
98+
ret_status[vm_name['hostname']] = {
99+
'ok' => true,
100+
}
101+
jobs_to_delete.push(req_hash)
102+
else
103+
puts "When using ABS you must delete all vms that you requested at the same time: Can't delete #{req_hash['request']['job']['id']}: #{hosts} does not include all of #{req_hash['allocated_resources']}"
104+
end
105+
end
106+
end
107+
end
108+
109+
response_body = {}
110+
111+
jobs_to_delete.each do |job|
112+
req_obj = {
113+
'job_id' => job['request']['job']['id'],
114+
'hosts' => job['allocated_resources'],
115+
}
116+
117+
puts "Deleting #{req_obj}" if verbose
118+
119+
return_result = conn.post 'return', req_obj.to_json
120+
req_obj['hosts'].each do |host|
121+
response_body[host['hostname']] = { 'ok' => true } if return_result.body == 'OK'
122+
end
123+
end
124+
125+
response_body
126+
end
127+
128+
# List available VMs in ABS
129+
def self.list(verbose, url, os_filter = nil)
130+
conn = Http.get_conn(verbose, url)
131+
132+
os_list = []
133+
134+
res = conn.get 'status/platforms/vmpooler'
135+
136+
res_body = JSON.parse(res.body)
137+
os_list << '*** VMPOOLER Pools ***'
138+
os_list += JSON.parse(res_body['vmpooler_platforms'])
139+
140+
res = conn.get 'status/platforms/nspooler'
141+
res_body = JSON.parse(res.body)
142+
os_list << ''
143+
os_list << '*** NSPOOLER Pools ***'
144+
os_list += JSON.parse(res_body['nspooler_platforms'])
145+
146+
res = conn.get 'status/platforms/aws'
147+
res_body = JSON.parse(res.body)
148+
os_list << ''
149+
os_list << '*** AWS Pools ***'
150+
os_list += JSON.parse(res_body['aws_platforms'])
151+
152+
os_list.delete 'ok'
153+
154+
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
155+
end
156+
157+
# Retrieve an OS from ABS.
158+
def self.retrieve(verbose, os_types, token, url, user, options)
159+
#
160+
# Contents of post must be like:
161+
#
162+
# {
163+
# "resources": {
164+
# "centos-7-i386": 1,
165+
# "ubuntu-1404-x86_64": 2
166+
# },
167+
# "job": {
168+
# "id": "12345",
169+
# "tags": {
170+
# "user": "username",
171+
# }
172+
# }
173+
# }
174+
175+
conn = Http.get_conn(verbose, url)
176+
conn.headers['X-AUTH-TOKEN'] = token if token
177+
178+
saved_job_id = DateTime.now.strftime('%Q')
179+
180+
req_obj = {
181+
:resources => os_types,
182+
:job => {
183+
:id => saved_job_id,
184+
:tags => {
185+
:user => user,
186+
},
187+
},
188+
}
189+
190+
if options['priority']
191+
req_obj[:priority] = if options['priority'] == 'high'
192+
1
193+
elsif options['priority'] == 'medium'
194+
2
195+
elsif options['priority'] == 'low'
196+
3
197+
else
198+
options['priority'].to_i
199+
end
200+
end
201+
202+
puts "Posting to ABS #{req_obj.to_json}" if verbose
203+
204+
# os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
205+
# raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
206+
puts "Requesting VMs with job_id: #{saved_job_id}. Will retry for up to an hour."
207+
res = conn.post 'request', req_obj.to_json
208+
209+
retries = 360
210+
211+
raise AuthError, "HTTP #{res.status}: The token provided could not authenticate to the pooler.\n#{res_body}" if res.status == 401
212+
213+
(1..retries).each do |i|
214+
queue_place, res_body = check_queue(conn, saved_job_id, req_obj)
215+
return translated(res_body) if res_body
216+
217+
sleep_seconds = 10 if i >= 10
218+
sleep_seconds = i if i < 10
219+
puts "Waiting #{sleep_seconds} seconds to check if ABS request has been filled. Queue Position: #{queue_place}... (x#{i})"
220+
221+
sleep(sleep_seconds)
222+
end
223+
nil
224+
end
225+
226+
#
227+
# We should fix the ABS API to be more like the vmpooler or nspooler api, but for now
228+
#
229+
def self.translated(res_body)
230+
vmpooler_formatted_body = {}
231+
232+
res_body.each do |host|
233+
if vmpooler_formatted_body[host['type']] && vmpooler_formatted_body[host['type']]['hostname'].class == Array
234+
vmpooler_formatted_body[host['type']]['hostname'] << host['hostname']
235+
else
236+
vmpooler_formatted_body[host['type']] = { 'hostname' => [host['hostname']] }
237+
end
238+
end
239+
vmpooler_formatted_body['ok'] = true
240+
241+
vmpooler_formatted_body
242+
end
243+
244+
def self.check_queue(conn, job_id, req_obj)
245+
queue_info_res = conn.get "status/queue/info/#{job_id}"
246+
queue_info = JSON.parse(queue_info_res.body)
247+
248+
res = conn.post 'request', req_obj.to_json
249+
250+
unless res.body.empty?
251+
res_body = JSON.parse(res.body)
252+
return queue_info['queue_place'], res_body
253+
end
254+
[queue_info['queue_place'], nil]
255+
end
256+
257+
def self.snapshot(_verbose, _url, _hostname, _token)
258+
puts "Can't snapshot with ABS, use '--service vmpooler' (even for vms checked out with ABS)"
259+
end
260+
261+
def self.status(verbose, url)
262+
conn = Http.get_conn(verbose, url)
263+
264+
res = conn.get 'status'
265+
266+
res.body == 'OK'
267+
end
268+
269+
def self.summary(verbose, url)
270+
conn = Http.get_conn(verbose, url)
271+
272+
res = conn.get 'summary'
273+
JSON.parse(res.body)
274+
end
275+
276+
def self.query(verbose, url, hostname)
277+
return @active_hostnames if @active_hostnames
278+
279+
puts "For vmpooler/snapshot information, use '--service vmpooler' (even for vms checked out with ABS)"
280+
conn = Http.get_conn(verbose, url)
281+
282+
res = conn.get "host/#{hostname}"
283+
JSON.parse(res.body)
284+
end
285+
end

lib/vmfloaty/nonstandard_pooler.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ def self.list(verbose, url, os_filter = nil)
1717
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
1818
end
1919

20-
def self.list_active(verbose, url, token)
20+
def self.list_active(verbose, url, token, _user)
2121
status = Auth.token_status(verbose, url, token)
2222
status['reserved_hosts'] || []
2323
end
2424

25-
def self.retrieve(verbose, os_type, token, url)
25+
def self.retrieve(verbose, os_type, token, url, _user, _options)
2626
conn = Http.get_conn(verbose, url)
2727
conn.headers['X-AUTH-TOKEN'] = token if token
2828

lib/vmfloaty/pooler.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def self.list(verbose, url, os_filter = nil)
2121
hosts
2222
end
2323

24-
def self.list_active(verbose, url, token)
24+
def self.list_active(verbose, url, token, _user)
2525
status = Auth.token_status(verbose, url, token)
2626
vms = []
2727
vms = status[token]['vms']['running'] if status[token] && status[token]['vms']
2828
vms
2929
end
3030

31-
def self.retrieve(verbose, os_type, token, url)
31+
def self.retrieve(verbose, os_type, token, url, _user, _options)
3232
# NOTE:
3333
# Developers can use `Utils.generate_os_hash` to
3434
# generate the os_type param.

0 commit comments

Comments
 (0)