mirror of
https://github.com/puppetlabs/vmfloaty.git
synced 2026-01-26 05:28:40 -05:00
Merge pull request #53 from mikkergimenez/add_abs_vm_get
Add abs vm get
This commit is contained in:
commit
6954321c87
13 changed files with 489 additions and 27 deletions
|
|
@ -14,10 +14,12 @@ Style/TrailingCommaInArrayLiteral:
|
||||||
Style/TrailingCommaInArguments:
|
Style/TrailingCommaInArguments:
|
||||||
EnforcedStyleForMultiline: comma
|
EnforcedStyleForMultiline: comma
|
||||||
|
|
||||||
Layout/AlignHash:
|
Layout/HashAlignment:
|
||||||
EnforcedHashRocketStyle: table
|
EnforcedHashRocketStyle: table
|
||||||
Layout/IndentFirstHashElement:
|
Layout/FirstHashElementIndentation:
|
||||||
EnforcedStyle: consistent
|
EnforcedStyle: consistent
|
||||||
|
Metrics/ParameterLists:
|
||||||
|
Enabled: False
|
||||||
|
|
||||||
Style/StderrPuts:
|
Style/StderrPuts:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring
|
||||||
|
|
||||||
### vmfloaty dotfile
|
### vmfloaty dotfile
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
#### Basic configuration
|
#### Basic configuration
|
||||||
|
|
||||||
|
|
@ -131,6 +131,11 @@ services:
|
||||||
url: 'https://nspooler.example.net/api/v1'
|
url: 'https://nspooler.example.net/api/v1'
|
||||||
token: 'nspooler-tokenstring'
|
token: 'nspooler-tokenstring'
|
||||||
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
|
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
|
||||||
|
abs:
|
||||||
|
url: 'https://abs.example.net/'
|
||||||
|
token: 'abs-tokenstring'
|
||||||
|
type: 'abs' # <-- 'type' is necessary for any non-vmpooler service
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With this configuration, you could list available OS types from nspooler like this:
|
With this configuration, you could list available OS types from nspooler like this:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ require 'vmfloaty/ssh'
|
||||||
class Vmfloaty
|
class Vmfloaty
|
||||||
include Commander::Methods
|
include Commander::Methods
|
||||||
|
|
||||||
def run
|
def run # rubocop:disable Metrics/AbcSize
|
||||||
program :version, Vmfloaty::VERSION
|
program :version, Vmfloaty::VERSION
|
||||||
program :description, 'A CLI helper tool for Puppet Labs VM poolers to help you stay afloat'
|
program :description, 'A CLI helper tool for Puppet Labs VM poolers to help you stay afloat'
|
||||||
|
|
||||||
|
|
@ -33,6 +33,7 @@ class Vmfloaty
|
||||||
c.option '--user STRING', String, 'User to authenticate with'
|
c.option '--user STRING', String, 'User to authenticate with'
|
||||||
c.option '--url STRING', String, 'URL of pooler service'
|
c.option '--url STRING', String, 'URL of pooler service'
|
||||||
c.option '--token STRING', String, 'Token for pooler service'
|
c.option '--token STRING', String, 'Token for pooler service'
|
||||||
|
c.option '--priority STRING', 'Priority for supported backends(ABS) (High(1), Medium(2), Low(3))'
|
||||||
c.option '--notoken', 'Makes a request without a token'
|
c.option '--notoken', 'Makes a request without a token'
|
||||||
c.option '--force', 'Forces vmfloaty to get requested vms'
|
c.option '--force', 'Forces vmfloaty to get requested vms'
|
||||||
c.option '--json', 'Prints retrieved vms in JSON format'
|
c.option '--json', 'Prints retrieved vms in JSON format'
|
||||||
|
|
@ -84,6 +85,7 @@ class Vmfloaty
|
||||||
c.option '--url STRING', String, 'URL of pooler service'
|
c.option '--url STRING', String, 'URL of pooler service'
|
||||||
c.action do |args, options|
|
c.action do |args, options|
|
||||||
verbose = options.verbose || config['verbose']
|
verbose = options.verbose || config['verbose']
|
||||||
|
|
||||||
service = Service.new(options, config)
|
service = Service.new(options, config)
|
||||||
filter = args[0]
|
filter = args[0]
|
||||||
|
|
||||||
|
|
|
||||||
285
lib/vmfloaty/abs.rb
Normal file
285
lib/vmfloaty/abs.rb
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'vmfloaty/errors'
|
||||||
|
require 'vmfloaty/http'
|
||||||
|
require 'faraday'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
class ABS
|
||||||
|
# List active VMs in ABS
|
||||||
|
# This is what a job request looks like:
|
||||||
|
# {
|
||||||
|
# "state":"filled",
|
||||||
|
# "last_processed":"2019-10-31 20:59:33 +0000",
|
||||||
|
# "allocated_resources": [
|
||||||
|
# {
|
||||||
|
# "hostname":"h3oyntawjm7xdch.delivery.puppetlabs.net",
|
||||||
|
# "type":"centos-7.2-tmpfs-x86_64",
|
||||||
|
# "engine":"vmpooler"}
|
||||||
|
# ],
|
||||||
|
# "audit_log":{
|
||||||
|
# "2019-10-30 20:33:12 +0000":"Allocated h3oyntawjm7xdch.delivery.puppetlabs.net for job 1572467589"
|
||||||
|
# },
|
||||||
|
# "request":{
|
||||||
|
# "resources":{
|
||||||
|
# "centos-7.2-tmpfs-x86_64":1
|
||||||
|
# },
|
||||||
|
# "job": {
|
||||||
|
# "id":1572467589,
|
||||||
|
# "tags": {
|
||||||
|
# "user":"mikker",
|
||||||
|
# "url_string":"floaty://mikker/1572467589"
|
||||||
|
# },
|
||||||
|
# "user":"mikker",
|
||||||
|
# "time-received":1572467589
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
|
||||||
|
@active_hostnames = {}
|
||||||
|
|
||||||
|
def self.list_active(verbose, url, _token, user)
|
||||||
|
all_jobs = []
|
||||||
|
@active_hostnames = {}
|
||||||
|
|
||||||
|
get_active_requests(verbose, url, user).each do |req_hash|
|
||||||
|
all_jobs.push(req_hash['request']['job']['id'])
|
||||||
|
@active_hostnames[req_hash['request']['job']['id']] = req_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
all_jobs
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.get_active_requests(verbose, url, user)
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
res = conn.get 'status/queue'
|
||||||
|
requests = JSON.parse(res.body)
|
||||||
|
|
||||||
|
ret_val = []
|
||||||
|
requests.each do |req|
|
||||||
|
req_hash = JSON.parse(req)
|
||||||
|
next unless user == req_hash['request']['job']['user']
|
||||||
|
|
||||||
|
ret_val.push(req_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
ret_val
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.all_job_resources_accounted_for(allocated_resources, hosts)
|
||||||
|
allocated_host_list = allocated_resources.map { |ar| ar['hostname'] }
|
||||||
|
(allocated_host_list - hosts).empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.delete(verbose, url, hosts, token, user)
|
||||||
|
# In ABS terms, this is a "returned" host.
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
conn.headers['X-AUTH-TOKEN'] = token if token
|
||||||
|
|
||||||
|
puts "Trying to delete hosts #{hosts}" if verbose
|
||||||
|
requests = get_active_requests(verbose, url, user)
|
||||||
|
|
||||||
|
jobs_to_delete = []
|
||||||
|
|
||||||
|
ret_status = {}
|
||||||
|
hosts.each do |host|
|
||||||
|
ret_status[host] = {
|
||||||
|
'ok' => false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
requests.each do |req_hash|
|
||||||
|
next unless req_hash['state'] == 'allocated' || req_hash['state'] == 'filled'
|
||||||
|
|
||||||
|
req_hash['allocated_resources'].each do |vm_name, _i|
|
||||||
|
if hosts.include? vm_name['hostname']
|
||||||
|
if all_job_resources_accounted_for(req_hash['allocated_resources'], hosts)
|
||||||
|
ret_status[vm_name['hostname']] = {
|
||||||
|
'ok' => true,
|
||||||
|
}
|
||||||
|
jobs_to_delete.push(req_hash)
|
||||||
|
else
|
||||||
|
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']}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
response_body = {}
|
||||||
|
|
||||||
|
jobs_to_delete.each do |job|
|
||||||
|
req_obj = {
|
||||||
|
'job_id' => job['request']['job']['id'],
|
||||||
|
'hosts' => job['allocated_resources'],
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Deleting #{req_obj}" if verbose
|
||||||
|
|
||||||
|
return_result = conn.post 'return', req_obj.to_json
|
||||||
|
req_obj['hosts'].each do |host|
|
||||||
|
response_body[host['hostname']] = { 'ok' => true } if return_result.body == 'OK'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
response_body
|
||||||
|
end
|
||||||
|
|
||||||
|
# List available VMs in ABS
|
||||||
|
def self.list(verbose, url, os_filter = nil)
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
|
||||||
|
os_list = []
|
||||||
|
|
||||||
|
res = conn.get 'status/platforms/vmpooler'
|
||||||
|
|
||||||
|
res_body = JSON.parse(res.body)
|
||||||
|
os_list << '*** VMPOOLER Pools ***'
|
||||||
|
os_list += JSON.parse(res_body['vmpooler_platforms'])
|
||||||
|
|
||||||
|
res = conn.get 'status/platforms/nspooler'
|
||||||
|
res_body = JSON.parse(res.body)
|
||||||
|
os_list << ''
|
||||||
|
os_list << '*** NSPOOLER Pools ***'
|
||||||
|
os_list += JSON.parse(res_body['nspooler_platforms'])
|
||||||
|
|
||||||
|
res = conn.get 'status/platforms/aws'
|
||||||
|
res_body = JSON.parse(res.body)
|
||||||
|
os_list << ''
|
||||||
|
os_list << '*** AWS Pools ***'
|
||||||
|
os_list += JSON.parse(res_body['aws_platforms'])
|
||||||
|
|
||||||
|
os_list.delete 'ok'
|
||||||
|
|
||||||
|
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
|
||||||
|
end
|
||||||
|
|
||||||
|
# Retrieve an OS from ABS.
|
||||||
|
def self.retrieve(verbose, os_types, token, url, user, options)
|
||||||
|
#
|
||||||
|
# Contents of post must be like:
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# "resources": {
|
||||||
|
# "centos-7-i386": 1,
|
||||||
|
# "ubuntu-1404-x86_64": 2
|
||||||
|
# },
|
||||||
|
# "job": {
|
||||||
|
# "id": "12345",
|
||||||
|
# "tags": {
|
||||||
|
# "user": "username",
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
conn.headers['X-AUTH-TOKEN'] = token if token
|
||||||
|
|
||||||
|
saved_job_id = DateTime.now.strftime('%Q')
|
||||||
|
|
||||||
|
req_obj = {
|
||||||
|
:resources => os_types,
|
||||||
|
:job => {
|
||||||
|
:id => saved_job_id,
|
||||||
|
:tags => {
|
||||||
|
:user => user,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if options['priority']
|
||||||
|
req_obj[:priority] = if options['priority'] == 'high'
|
||||||
|
1
|
||||||
|
elsif options['priority'] == 'medium'
|
||||||
|
2
|
||||||
|
elsif options['priority'] == 'low'
|
||||||
|
3
|
||||||
|
else
|
||||||
|
options['priority'].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Posting to ABS #{req_obj.to_json}" if verbose
|
||||||
|
|
||||||
|
# os_string = os_type.map { |os, num| Array(os) * num }.flatten.join('+')
|
||||||
|
# raise MissingParamError, 'No operating systems provided to obtain.' if os_string.empty?
|
||||||
|
puts "Requesting VMs with job_id: #{saved_job_id}. Will retry for up to an hour."
|
||||||
|
res = conn.post 'request', req_obj.to_json
|
||||||
|
|
||||||
|
retries = 360
|
||||||
|
|
||||||
|
raise AuthError, "HTTP #{res.status}: The token provided could not authenticate to the pooler.\n#{res_body}" if res.status == 401
|
||||||
|
|
||||||
|
(1..retries).each do |i|
|
||||||
|
queue_place, res_body = check_queue(conn, saved_job_id, req_obj)
|
||||||
|
return translated(res_body) if res_body
|
||||||
|
|
||||||
|
sleep_seconds = 10 if i >= 10
|
||||||
|
sleep_seconds = i if i < 10
|
||||||
|
puts "Waiting #{sleep_seconds} seconds to check if ABS request has been filled. Queue Position: #{queue_place}... (x#{i})"
|
||||||
|
|
||||||
|
sleep(sleep_seconds)
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# We should fix the ABS API to be more like the vmpooler or nspooler api, but for now
|
||||||
|
#
|
||||||
|
def self.translated(res_body)
|
||||||
|
vmpooler_formatted_body = {}
|
||||||
|
|
||||||
|
res_body.each do |host|
|
||||||
|
if vmpooler_formatted_body[host['type']] && vmpooler_formatted_body[host['type']]['hostname'].class == Array
|
||||||
|
vmpooler_formatted_body[host['type']]['hostname'] << host['hostname']
|
||||||
|
else
|
||||||
|
vmpooler_formatted_body[host['type']] = { 'hostname' => [host['hostname']] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vmpooler_formatted_body['ok'] = true
|
||||||
|
|
||||||
|
vmpooler_formatted_body
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.check_queue(conn, job_id, req_obj)
|
||||||
|
queue_info_res = conn.get "status/queue/info/#{job_id}"
|
||||||
|
queue_info = JSON.parse(queue_info_res.body)
|
||||||
|
|
||||||
|
res = conn.post 'request', req_obj.to_json
|
||||||
|
|
||||||
|
unless res.body.empty?
|
||||||
|
res_body = JSON.parse(res.body)
|
||||||
|
return queue_info['queue_place'], res_body
|
||||||
|
end
|
||||||
|
[queue_info['queue_place'], nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.snapshot(_verbose, _url, _hostname, _token)
|
||||||
|
puts "Can't snapshot with ABS, use '--service vmpooler' (even for vms checked out with ABS)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.status(verbose, url)
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
|
||||||
|
res = conn.get 'status'
|
||||||
|
|
||||||
|
res.body == 'OK'
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.summary(verbose, url)
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
|
||||||
|
res = conn.get 'summary'
|
||||||
|
JSON.parse(res.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.query(verbose, url, hostname)
|
||||||
|
return @active_hostnames if @active_hostnames
|
||||||
|
|
||||||
|
puts "For vmpooler/snapshot information, use '--service vmpooler' (even for vms checked out with ABS)"
|
||||||
|
conn = Http.get_conn(verbose, url)
|
||||||
|
|
||||||
|
res = conn.get "host/#{hostname}"
|
||||||
|
JSON.parse(res.body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -17,12 +17,12 @@ class NonstandardPooler
|
||||||
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
|
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list_active(verbose, url, token)
|
def self.list_active(verbose, url, token, _user)
|
||||||
status = Auth.token_status(verbose, url, token)
|
status = Auth.token_status(verbose, url, token)
|
||||||
status['reserved_hosts'] || []
|
status['reserved_hosts'] || []
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.retrieve(verbose, os_type, token, url)
|
def self.retrieve(verbose, os_type, token, url, _user, _options)
|
||||||
conn = Http.get_conn(verbose, url)
|
conn = Http.get_conn(verbose, url)
|
||||||
conn.headers['X-AUTH-TOKEN'] = token if token
|
conn.headers['X-AUTH-TOKEN'] = token if token
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ class Pooler
|
||||||
hosts
|
hosts
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list_active(verbose, url, token)
|
def self.list_active(verbose, url, token, _user)
|
||||||
status = Auth.token_status(verbose, url, token)
|
status = Auth.token_status(verbose, url, token)
|
||||||
vms = []
|
vms = []
|
||||||
vms = status[token]['vms']['running'] if status[token] && status[token]['vms']
|
vms = status[token]['vms']['running'] if status[token] && status[token]['vms']
|
||||||
vms
|
vms
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.retrieve(verbose, os_type, token, url)
|
def self.retrieve(verbose, os_type, token, url, _user, _options)
|
||||||
# NOTE:
|
# NOTE:
|
||||||
# Developers can use `Utils.generate_os_hash` to
|
# Developers can use `Utils.generate_os_hash` to
|
||||||
# generate the os_type param.
|
# generate the os_type param.
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class Service
|
||||||
|
|
||||||
def user
|
def user
|
||||||
unless @config['user']
|
unless @config['user']
|
||||||
puts 'Enter your pooler service username:'
|
puts "Enter your #{@config['url']} service username:"
|
||||||
@config['user'] = STDIN.gets.chomp
|
@config['user'] = STDIN.gets.chomp
|
||||||
end
|
end
|
||||||
@config['user']
|
@config['user']
|
||||||
|
|
@ -52,13 +52,13 @@ class Service
|
||||||
|
|
||||||
def get_new_token(verbose)
|
def get_new_token(verbose)
|
||||||
username = user
|
username = user
|
||||||
pass = Commander::UI.password 'Enter your pooler service password:', '*'
|
pass = Commander::UI.password "Enter your #{@config['url']} service password:", '*'
|
||||||
Auth.get_token(verbose, url, username, pass)
|
Auth.get_token(verbose, url, username, pass)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_token(verbose, token_value = @config['token'])
|
def delete_token(verbose, token_value = @config['token'])
|
||||||
username = user
|
username = user
|
||||||
pass = Commander::UI.password 'Enter your pooler service password:', '*'
|
pass = Commander::UI.password "Enter your #{@config['url']} service password:", '*'
|
||||||
Auth.delete_token(verbose, url, username, pass, token_value)
|
Auth.delete_token(verbose, url, username, pass, token_value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -72,13 +72,13 @@ class Service
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_active(verbose)
|
def list_active(verbose)
|
||||||
@service_object.list_active verbose, url, token
|
@service_object.list_active verbose, url, token, user
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve(verbose, os_types, use_token = true)
|
def retrieve(verbose, os_types, use_token = true)
|
||||||
puts 'Requesting a vm without a token...' unless use_token
|
puts 'Requesting a vm without a token...' unless use_token
|
||||||
token_value = use_token ? token : nil
|
token_value = use_token ? token : nil
|
||||||
@service_object.retrieve verbose, os_types, token_value, url
|
@service_object.retrieve verbose, os_types, token_value, url, user, @config
|
||||||
end
|
end
|
||||||
|
|
||||||
def ssh(verbose, host_os, use_token = true)
|
def ssh(verbose, host_os, use_token = true)
|
||||||
|
|
@ -112,7 +112,7 @@ class Service
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(verbose, hosts)
|
def delete(verbose, hosts)
|
||||||
@service_object.delete verbose, url, hosts, token
|
@service_object.delete verbose, url, hosts, token, user
|
||||||
end
|
end
|
||||||
|
|
||||||
def status(verbose)
|
def status(verbose)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'vmfloaty/pooler'
|
require 'vmfloaty/abs'
|
||||||
require 'vmfloaty/nonstandard_pooler'
|
require 'vmfloaty/nonstandard_pooler'
|
||||||
|
require 'vmfloaty/pooler'
|
||||||
|
|
||||||
class Utils
|
class Utils
|
||||||
# TODO: Takes the json response body from an HTTP GET
|
# TODO: Takes the json response body from an HTTP GET
|
||||||
|
|
@ -30,6 +31,13 @@ class Utils
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
# abs pooler response body example when `floaty get` arguments are :
|
||||||
|
# {
|
||||||
|
# "hostname"=>"thin-soutane.delivery.puppetlabs.net",
|
||||||
|
# "type"=>"centos-7.2-tmpfs-x86_64",
|
||||||
|
# "engine"=>"vmpooler"
|
||||||
|
# }
|
||||||
|
|
||||||
raise ArgumentError, "Bad GET response passed to format_hosts: #{response_body.to_json}" unless response_body.delete('ok')
|
raise ArgumentError, "Bad GET response passed to format_hosts: #{response_body.to_json}" unless response_body.delete('ok')
|
||||||
|
|
||||||
# vmpooler reports the domain separately from the hostname
|
# vmpooler reports the domain separately from the hostname
|
||||||
|
|
@ -77,6 +85,13 @@ class Utils
|
||||||
host_data = response[hostname]
|
host_data = response[hostname]
|
||||||
|
|
||||||
case service.type
|
case service.type
|
||||||
|
when 'ABS'
|
||||||
|
# For ABS, 'hostname' variable is the jobID
|
||||||
|
if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
|
||||||
|
host_data['allocated_resources'].each do |vm_name, _i|
|
||||||
|
puts "- [JobID:#{host_data['request']['job']['id']}] #{vm_name['hostname']} (#{vm_name['type']}) <#{host_data['state']}>"
|
||||||
|
end
|
||||||
|
end
|
||||||
when 'Pooler'
|
when 'Pooler'
|
||||||
tag_pairs = []
|
tag_pairs = []
|
||||||
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
||||||
|
|
@ -140,6 +155,9 @@ class Utils
|
||||||
puts "#{name.ljust(width)} #{e.red}"
|
puts "#{name.ljust(width)} #{e.red}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
when 'ABS'
|
||||||
|
puts 'ABS Not OK'.red unless status_response
|
||||||
|
puts 'ABS is OK'.green if status_response
|
||||||
else
|
else
|
||||||
raise "Invalid service type #{service.type}"
|
raise "Invalid service type #{service.type}"
|
||||||
end
|
end
|
||||||
|
|
@ -155,8 +173,11 @@ class Utils
|
||||||
|
|
||||||
def self.get_service_object(type = '')
|
def self.get_service_object(type = '')
|
||||||
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
||||||
|
abs_strings = %w[abs alwaysbescheduling always_be_scheduling]
|
||||||
if nspooler_strings.include? type.downcase
|
if nspooler_strings.include? type.downcase
|
||||||
NonstandardPooler
|
NonstandardPooler
|
||||||
|
elsif abs_strings.include? type.downcase
|
||||||
|
ABS
|
||||||
else
|
else
|
||||||
Pooler
|
Pooler
|
||||||
end
|
end
|
||||||
|
|
@ -187,6 +208,7 @@ class Utils
|
||||||
end
|
end
|
||||||
|
|
||||||
# Prioritize an explicitly specified url, user, or token if the user provided one
|
# Prioritize an explicitly specified url, user, or token if the user provided one
|
||||||
|
service_config['priority'] = options.priority unless options.priority.nil?
|
||||||
service_config['url'] = options.url unless options.url.nil?
|
service_config['url'] = options.url unless options.url.nil?
|
||||||
service_config['token'] = options.token unless options.token.nil?
|
service_config['token'] = options.token unless options.token.nil?
|
||||||
service_config['user'] = options.user unless options.user.nil?
|
service_config['user'] = options.user unless options.user.nil?
|
||||||
|
|
|
||||||
84
spec/vmfloaty/abs/auth_spec.rb
Normal file
84
spec/vmfloaty/abs/auth_spec.rb
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require_relative '../../../lib/vmfloaty/auth'
|
||||||
|
|
||||||
|
describe Pooler do
|
||||||
|
before :each do
|
||||||
|
@abs_url = 'https://abs.example.com/api/v2'
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#get_token' do
|
||||||
|
before :each do
|
||||||
|
@get_token_response = '{"ok": true,"token":"utpg2i2xswor6h8ttjhu3d47z53yy47y"}'
|
||||||
|
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a token from abs' do
|
||||||
|
stub_request(:post, 'https://first.last:password@abs.example.com/api/v2/token')
|
||||||
|
.to_return(:status => 200, :body => @get_token_response, :headers => {})
|
||||||
|
|
||||||
|
token = Auth.get_token(false, @abs_url, 'first.last', 'password')
|
||||||
|
expect(token).to eq @token
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a token error if something goes wrong' do
|
||||||
|
stub_request(:post, 'https://first.last:password@abs.example.com/api/v2/token')
|
||||||
|
.to_return(:status => 500, :body => '{"ok":false}', :headers => {})
|
||||||
|
|
||||||
|
expect { Auth.get_token(false, @abs_url, 'first.last', 'password') }.to raise_error(TokenError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#delete_token' do
|
||||||
|
before :each do
|
||||||
|
@delete_token_response = '{"ok":true}'
|
||||||
|
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the specified token' do
|
||||||
|
stub_request(:delete, 'https://first.last:password@abs.example.com/api/v2/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
|
||||||
|
.to_return(:status => 200, :body => @delete_token_response, :headers => {})
|
||||||
|
|
||||||
|
expect(Auth.delete_token(false, @abs_url, 'first.last', 'password', @token)).to eq JSON.parse(@delete_token_response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a token error if something goes wrong' do
|
||||||
|
stub_request(:delete, 'https://first.last:password@abs.example.com/api/v2/token/utpg2i2xswor6h8ttjhu3d47z53yy47y')
|
||||||
|
.to_return(:status => 500, :body => '{"ok":false}', :headers => {})
|
||||||
|
|
||||||
|
expect { Auth.delete_token(false, @abs_url, 'first.last', 'password', @token) }.to raise_error(TokenError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a token error if no token provided' do
|
||||||
|
expect { Auth.delete_token(false, @abs_url, 'first.last', 'password', nil) }.to raise_error(TokenError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#token_status' do
|
||||||
|
before :each do
|
||||||
|
@token_status_response = '{"ok":true,"utpg2i2xswor6h8ttjhu3d47z53yy47y":{"created":"2015-04-28 19:17:47 -0700"}}'
|
||||||
|
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'checks the status of a token' do
|
||||||
|
stub_request(:get, "#{@abs_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
|
||||||
|
.with(:headers => { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3' })
|
||||||
|
.to_return(:status => 200, :body => @token_status_response, :headers => {})
|
||||||
|
|
||||||
|
expect(Auth.token_status(false, @abs_url, @token)).to eq JSON.parse(@token_status_response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a token error if something goes wrong' do
|
||||||
|
stub_request(:get, "#{@abs_url}/token/utpg2i2xswor6h8ttjhu3d47z53yy47y")
|
||||||
|
.with(:headers => { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3' })
|
||||||
|
.to_return(:status => 500, :body => '{"ok":false}', :headers => {})
|
||||||
|
|
||||||
|
expect { Auth.token_status(false, @abs_url, @token) }.to raise_error(TokenError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises a token error if no token provided' do
|
||||||
|
expect { Auth.token_status(false, @abs_url, nil) }.to raise_error(TokenError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
62
spec/vmfloaty/abs_spec.rb
Normal file
62
spec/vmfloaty/abs_spec.rb
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'vmfloaty/utils'
|
||||||
|
require 'vmfloaty/errors'
|
||||||
|
require 'vmfloaty/abs'
|
||||||
|
|
||||||
|
describe ABS do
|
||||||
|
before :each do
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#format' do
|
||||||
|
it 'returns an hash formatted like a vmpooler return' do
|
||||||
|
abs_formatted_response = [
|
||||||
|
{ 'hostname' => 'aaaaaaaaaaaaaaa.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64', 'engine' => 'vmpooler' },
|
||||||
|
{ 'hostname' => 'aaaaaaaaaaaaaab.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64', 'engine' => 'vmpooler' },
|
||||||
|
{ 'hostname' => 'aaaaaaaaaaaaaac.delivery.puppetlabs.net', 'type' => 'ubuntu-7.2-x86_64', 'engine' => 'vmpooler' },
|
||||||
|
]
|
||||||
|
|
||||||
|
vmpooler_formatted_response = ABS.translated(abs_formatted_response)
|
||||||
|
|
||||||
|
vmpooler_formatted_compare = {
|
||||||
|
'centos-7.2-x86_64' => {},
|
||||||
|
'ubuntu-7.2-x86_64' => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
vmpooler_formatted_compare['centos-7.2-x86_64']['hostname'] = ['aaaaaaaaaaaaaaa.delivery.puppetlabs.net', 'aaaaaaaaaaaaaab.delivery.puppetlabs.net']
|
||||||
|
vmpooler_formatted_compare['ubuntu-7.2-x86_64']['hostname'] = ['aaaaaaaaaaaaaac.delivery.puppetlabs.net']
|
||||||
|
|
||||||
|
vmpooler_formatted_compare['ok'] = true
|
||||||
|
|
||||||
|
expect(vmpooler_formatted_response).to eq(vmpooler_formatted_compare)
|
||||||
|
vmpooler_formatted_response.delete('ok')
|
||||||
|
vmpooler_formatted_compare.delete('ok')
|
||||||
|
expect(vmpooler_formatted_response).to eq(vmpooler_formatted_compare)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'won\'t delete a job if not all vms are listed' do
|
||||||
|
hosts = ['host1']
|
||||||
|
allocated_resources = [
|
||||||
|
{
|
||||||
|
'hostname' => 'host1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'hostname' => 'host2',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
expect(ABS.all_job_resources_accounted_for(allocated_resources, hosts)).to eq(false)
|
||||||
|
|
||||||
|
hosts = ['host1', 'host2']
|
||||||
|
allocated_resources = [
|
||||||
|
{
|
||||||
|
'hostname' => 'host1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'hostname' => 'host2',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
expect(ABS.all_job_resources_accounted_for(allocated_resources, hosts)).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -88,7 +88,7 @@ describe NonstandardPooler do
|
||||||
.with(false, @nspooler_url, 'token-value')
|
.with(false, @nspooler_url, 'token-value')
|
||||||
.and_return(JSON.parse(@token_status_body_active))
|
.and_return(JSON.parse(@token_status_body_active))
|
||||||
|
|
||||||
list = NonstandardPooler.list_active(false, @nspooler_url, 'token-value')
|
list = NonstandardPooler.list_active(false, @nspooler_url, 'token-value', 'user')
|
||||||
expect(list).to eql ['sol10-9', 'sol10-11']
|
expect(list).to eql ['sol10-9', 'sol10-11']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -125,7 +125,7 @@ describe NonstandardPooler do
|
||||||
.to_return(:status => 401, :body => '{"ok":false,"reason": "token: token-value does not exist"}', :headers => {})
|
.to_return(:status => 401, :body => '{"ok":false,"reason": "token: token-value does not exist"}', :headers => {})
|
||||||
|
|
||||||
vm_hash = { 'solaris-11-sparc' => 1 }
|
vm_hash = { 'solaris-11-sparc' => 1 }
|
||||||
expect { NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url) }.to raise_error(AuthError)
|
expect { NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {}) }.to raise_error(AuthError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'retrieves a single vm with a token' do
|
it 'retrieves a single vm with a token' do
|
||||||
|
|
@ -134,7 +134,7 @@ describe NonstandardPooler do
|
||||||
.to_return(:status => 200, :body => @retrieve_response_body_single, :headers => {})
|
.to_return(:status => 200, :body => @retrieve_response_body_single, :headers => {})
|
||||||
|
|
||||||
vm_hash = { 'solaris-11-sparc' => 1 }
|
vm_hash = { 'solaris-11-sparc' => 1 }
|
||||||
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
|
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {})
|
||||||
expect(vm_req).to be_an_instance_of Hash
|
expect(vm_req).to be_an_instance_of Hash
|
||||||
expect(vm_req['ok']).to equal true
|
expect(vm_req['ok']).to equal true
|
||||||
expect(vm_req['solaris-11-sparc']['hostname']).to eq 'sol11-4.delivery.puppetlabs.net'
|
expect(vm_req['solaris-11-sparc']['hostname']).to eq 'sol11-4.delivery.puppetlabs.net'
|
||||||
|
|
@ -146,7 +146,7 @@ describe NonstandardPooler do
|
||||||
.to_return(:status => 200, :body => @retrieve_response_body_many, :headers => {})
|
.to_return(:status => 200, :body => @retrieve_response_body_many, :headers => {})
|
||||||
|
|
||||||
vm_hash = { 'aix-7.1-power' => 1, 'solaris-10-sparc' => 2 }
|
vm_hash = { 'aix-7.1-power' => 1, 'solaris-10-sparc' => 2 }
|
||||||
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
|
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url, 'first.last', {})
|
||||||
expect(vm_req).to be_an_instance_of Hash
|
expect(vm_req).to be_an_instance_of Hash
|
||||||
expect(vm_req['ok']).to equal true
|
expect(vm_req['ok']).to equal true
|
||||||
expect(vm_req['solaris-10-sparc']['hostname']).to be_an_instance_of Array
|
expect(vm_req['solaris-10-sparc']['hostname']).to be_an_instance_of Array
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ describe Pooler do
|
||||||
|
|
||||||
vm_hash = {}
|
vm_hash = {}
|
||||||
vm_hash['debian-7-i386'] = 1
|
vm_hash['debian-7-i386'] = 1
|
||||||
expect { Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url) }.to raise_error(AuthError)
|
expect { Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {}) }.to raise_error(AuthError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'retrieves a single vm with a token' do
|
it 'retrieves a single vm with a token' do
|
||||||
|
|
@ -63,7 +63,7 @@ describe Pooler do
|
||||||
|
|
||||||
vm_hash = {}
|
vm_hash = {}
|
||||||
vm_hash['debian-7-i386'] = 1
|
vm_hash['debian-7-i386'] = 1
|
||||||
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url)
|
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {})
|
||||||
expect(vm_req).to be_an_instance_of Hash
|
expect(vm_req).to be_an_instance_of Hash
|
||||||
expect(vm_req['ok']).to equal true
|
expect(vm_req['ok']).to equal true
|
||||||
expect(vm_req['debian-7-i386']['hostname']).to eq 'fq6qlpjlsskycq6'
|
expect(vm_req['debian-7-i386']['hostname']).to eq 'fq6qlpjlsskycq6'
|
||||||
|
|
@ -77,7 +77,7 @@ describe Pooler do
|
||||||
vm_hash = {}
|
vm_hash = {}
|
||||||
vm_hash['debian-7-i386'] = 2
|
vm_hash['debian-7-i386'] = 2
|
||||||
vm_hash['centos-7-x86_64'] = 1
|
vm_hash['centos-7-x86_64'] = 1
|
||||||
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url)
|
vm_req = Pooler.retrieve(false, vm_hash, 'mytokenfile', @vmpooler_url, 'user', {})
|
||||||
expect(vm_req).to be_an_instance_of Hash
|
expect(vm_req).to be_an_instance_of Hash
|
||||||
expect(vm_req['ok']).to equal true
|
expect(vm_req['ok']).to equal true
|
||||||
expect(vm_req['debian-7-i386']['hostname']).to be_an_instance_of Array
|
expect(vm_req['debian-7-i386']['hostname']).to be_an_instance_of Array
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ describe Service do
|
||||||
it 'prompts the user for their password and retrieves a token' do
|
it 'prompts the user for their password and retrieves a token' do
|
||||||
config = { 'user' => 'first.last', 'url' => 'http://default.url' }
|
config = { 'user' => 'first.last', 'url' => 'http://default.url' }
|
||||||
service = Service.new(MockOptions.new, config)
|
service = Service.new(MockOptions.new, config)
|
||||||
allow(STDOUT).to receive(:puts).with('Enter your pooler service password:')
|
allow(STDOUT).to receive(:puts).with('Enter your http://default.url service password:')
|
||||||
allow(Commander::UI).to(receive(:password)
|
allow(Commander::UI).to(receive(:password)
|
||||||
.with('Enter your pooler service password:', '*')
|
.with('Enter your http://default.url service password:', '*')
|
||||||
.and_return('hunter2'))
|
.and_return('hunter2'))
|
||||||
allow(Auth).to(receive(:get_token)
|
allow(Auth).to(receive(:get_token)
|
||||||
.with(nil, config['url'], config['user'], 'hunter2')
|
.with(nil, config['url'], config['user'], 'hunter2')
|
||||||
|
|
@ -29,11 +29,11 @@ describe Service do
|
||||||
it 'prompts the user for their username and password if the username is unknown' do
|
it 'prompts the user for their username and password if the username is unknown' do
|
||||||
config = { 'url' => 'http://default.url' }
|
config = { 'url' => 'http://default.url' }
|
||||||
service = Service.new(MockOptions.new({}), config)
|
service = Service.new(MockOptions.new({}), config)
|
||||||
allow(STDOUT).to receive(:puts).with 'Enter your pooler service username:'
|
allow(STDOUT).to receive(:puts).with 'Enter your http://default.url service username:'
|
||||||
allow(STDOUT).to receive(:puts).with "\n"
|
allow(STDOUT).to receive(:puts).with "\n"
|
||||||
allow(STDIN).to receive(:gets).and_return('first.last')
|
allow(STDIN).to receive(:gets).and_return('first.last')
|
||||||
allow(Commander::UI).to(receive(:password)
|
allow(Commander::UI).to(receive(:password)
|
||||||
.with('Enter your pooler service password:', '*')
|
.with('Enter your http://default.url service password:', '*')
|
||||||
.and_return('hunter2'))
|
.and_return('hunter2'))
|
||||||
allow(Auth).to(receive(:get_token)
|
allow(Auth).to(receive(:get_token)
|
||||||
.with(nil, config['url'], 'first.last', 'hunter2')
|
.with(nil, config['url'], 'first.last', 'hunter2')
|
||||||
|
|
@ -46,7 +46,7 @@ describe Service do
|
||||||
it 'deletes a token' do
|
it 'deletes a token' do
|
||||||
service = Service.new(MockOptions.new, 'user' => 'first.last', 'url' => 'http://default.url')
|
service = Service.new(MockOptions.new, 'user' => 'first.last', 'url' => 'http://default.url')
|
||||||
allow(Commander::UI).to(receive(:password)
|
allow(Commander::UI).to(receive(:password)
|
||||||
.with('Enter your pooler service password:', '*')
|
.with('Enter your http://default.url service password:', '*')
|
||||||
.and_return('hunter2'))
|
.and_return('hunter2'))
|
||||||
allow(Auth).to(receive(:delete_token)
|
allow(Auth).to(receive(:delete_token)
|
||||||
.with(nil, 'http://default.url', 'first.last', 'hunter2', 'token-value')
|
.with(nil, 'http://default.url', 'first.last', 'hunter2', 'token-value')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue