mirror of
https://github.com/puppetlabs/vmfloaty.git
synced 2026-01-25 21:28:40 -05:00
Merge pull request #45 from caseywilliams/nspooler-integration
Add configuration for multiple pooler services and integration with nspooler
This commit is contained in:
commit
44d573301a
12 changed files with 1312 additions and 462 deletions
64
README.md
64
README.md
|
|
@ -68,6 +68,8 @@ floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring
|
|||
|
||||
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:
|
||||
|
||||
#### Basic configuration
|
||||
|
||||
```yaml
|
||||
# file at /Users/me/.vmfloaty.yml
|
||||
url: 'https://vmpooler.mycompany.net/api/v1'
|
||||
|
|
@ -77,6 +79,66 @@ token: 'tokenstring'
|
|||
|
||||
Now vmfloaty will use those config files if no flag was specified.
|
||||
|
||||
#### Configuring multiple services
|
||||
|
||||
Most commands allow you to specify a `--service <servicename>` option to allow the use of multiple vmpooler instances. This can be useful when you'd rather not specify a `--url` or `--token` by hand for alternate services.
|
||||
|
||||
To configure multiple services, you can set up your `~/.vmfloaty.yml` config file like this:
|
||||
|
||||
```yaml
|
||||
# file at /Users/me/.vmfloaty.yml
|
||||
user: 'brian'
|
||||
services:
|
||||
main:
|
||||
url: 'https://vmpooler.mycompany.net/api/v1'
|
||||
token: 'tokenstring'
|
||||
alternate:
|
||||
url: 'https://vmpooler.alternate.net/api/v1'
|
||||
token: 'alternate-tokenstring'
|
||||
```
|
||||
|
||||
- If you run `floaty` without a `--service <name>` option, vmfloaty will use the first configured service by default.
|
||||
With the config file above, the default would be to use the 'main' vmpooler instance.
|
||||
- If keys are missing for a configured service, vmfloaty will attempt to fall back to the top-level values.
|
||||
With the config file above, 'brian' will be used as the username for both configured services, since neither specifies a username.
|
||||
|
||||
Examples using the above configuration:
|
||||
|
||||
List available vm types from our main vmpooler instance:
|
||||
```sh
|
||||
floaty list --service main
|
||||
# or, since the first configured service is used by default:
|
||||
floaty list
|
||||
```
|
||||
|
||||
List available vm types from our alternate vmpooler instance:
|
||||
```sh
|
||||
floaty list --service alternate
|
||||
```
|
||||
|
||||
#### Using a Nonstandard Pooler service
|
||||
|
||||
vmfloaty is capable of working with Puppet's [nonstandard pooler](https://github.com/puppetlabs/nspooler) in addition to the default vmpooler API. To add a nonstandard pooler service, specify an API `type` value in your service configuration, like this:
|
||||
|
||||
```yaml
|
||||
# file at /Users/me/.vmfloaty.yml
|
||||
user: 'brian'
|
||||
services:
|
||||
vm:
|
||||
url: 'https://vmpooler.mycompany.net/api/v1'
|
||||
token: 'tokenstring'
|
||||
ns:
|
||||
url: 'https://nspooler.mycompany.net/api/v1'
|
||||
token: 'nspooler-tokenstring'
|
||||
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
|
||||
```
|
||||
|
||||
With this configuration, you could list available OS types from nspooler like this:
|
||||
|
||||
```sh
|
||||
floaty list --service ns
|
||||
```
|
||||
|
||||
#### Valid config keys
|
||||
|
||||
Here are the keys that vmfloaty currently supports:
|
||||
|
|
@ -89,6 +151,8 @@ Here are the keys that vmfloaty currently supports:
|
|||
+ String
|
||||
- url
|
||||
+ String
|
||||
- services
|
||||
+ Map
|
||||
|
||||
### Tab Completion
|
||||
|
||||
|
|
|
|||
506
lib/vmfloaty.rb
506
lib/vmfloaty.rb
|
|
@ -5,11 +5,13 @@ require 'commander'
|
|||
require 'colorize'
|
||||
require 'json'
|
||||
require 'pp'
|
||||
require 'uri'
|
||||
require 'vmfloaty/auth'
|
||||
require 'vmfloaty/pooler'
|
||||
require 'vmfloaty/version'
|
||||
require 'vmfloaty/conf'
|
||||
require 'vmfloaty/utils'
|
||||
require 'vmfloaty/service'
|
||||
require 'vmfloaty/ssh'
|
||||
|
||||
class Vmfloaty
|
||||
|
|
@ -17,27 +19,26 @@ class Vmfloaty
|
|||
|
||||
def run
|
||||
program :version, Vmfloaty::VERSION
|
||||
program :description, 'A CLI helper tool for Puppet Labs vmpooler to help you stay afloat'
|
||||
program :description, 'A CLI helper tool for Puppet Labs VM poolers to help you stay afloat'
|
||||
|
||||
config = Conf.read_config
|
||||
|
||||
command :get do |c|
|
||||
c.syntax = 'floaty get os_type0 os_type1=x ox_type2=y [options]'
|
||||
c.summary = 'Gets a vm or vms based on the os argument'
|
||||
c.description = 'A command to retrieve vms from vmpooler. Can either be a single vm, or multiple with the `=` syntax.'
|
||||
c.description = 'A command to retrieve vms from a pooler service. Can either be a single vm, or multiple with the `=` syntax.'
|
||||
c.example 'Gets a few vms', 'floaty get centos=3 debian --user brian --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--user STRING', String, 'User to authenticate with'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--notoken', 'Makes a request without a token'
|
||||
c.option '--force', 'Forces vmfloaty to get requested vms'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
token = options.token || config['token']
|
||||
user = options.user ||= config['user']
|
||||
url = options.url ||= config['url']
|
||||
no_token = options.notoken
|
||||
service = Service.new(options, config)
|
||||
use_token = !options.notoken
|
||||
force = options.force
|
||||
|
||||
if args.empty?
|
||||
|
|
@ -48,98 +49,51 @@ class Vmfloaty
|
|||
os_types = Utils.generate_os_hash(args)
|
||||
|
||||
max_pool_request = 5
|
||||
large_pool_requests = os_types.select{|k,v| v > max_pool_request}
|
||||
large_pool_requests = os_types.select{|_,v| v > max_pool_request}
|
||||
if ! large_pool_requests.empty? and ! force
|
||||
STDERR.puts "Requesting vms over #{max_pool_request} requires a --force flag."
|
||||
STDERR.puts "Try again with `floaty get --force`"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless os_types.empty?
|
||||
if no_token
|
||||
begin
|
||||
response = Pooler.retrieve(verbose, os_types, nil, url)
|
||||
rescue MissingParamError
|
||||
STDERR.puts e
|
||||
STDERR.puts "See `floaty get --help` for more information on how to get VMs."
|
||||
rescue AuthError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
puts Utils.format_hosts(response)
|
||||
exit 0
|
||||
else
|
||||
unless token
|
||||
puts "No token found. Retrieving a token..."
|
||||
if !user
|
||||
STDERR.puts "You did not provide a user to authenticate to vmpooler with"
|
||||
exit 1
|
||||
end
|
||||
pass = password "Enter your vmpooler password please:", '*'
|
||||
begin
|
||||
token = Auth.get_token(verbose, url, user, pass)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "\nToken retrieved!"
|
||||
puts token
|
||||
end
|
||||
|
||||
begin
|
||||
response = Pooler.retrieve(verbose, os_types, token, url)
|
||||
rescue MissingParamError
|
||||
STDERR.puts e
|
||||
STDERR.puts "See `floaty get --help` for more information on how to get VMs."
|
||||
rescue AuthError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
puts Utils.format_hosts(response)
|
||||
exit 0
|
||||
end
|
||||
else
|
||||
if os_types.empty?
|
||||
STDERR.puts "No operating systems provided to obtain. See `floaty get --help` for more information on how to get VMs."
|
||||
exit 1
|
||||
end
|
||||
|
||||
response = service.retrieve(verbose, os_types, use_token)
|
||||
puts Utils.format_hosts(response)
|
||||
end
|
||||
end
|
||||
|
||||
command :list do |c|
|
||||
c.syntax = 'floaty list [options]'
|
||||
c.summary = 'Shows a list of available vms from the pooler or vms obtained with a token'
|
||||
c.description = 'List will either show all vm templates available in vmpooler, or with the --active flag it will list vms obtained with a vmpooler token.'
|
||||
c.description = 'List will either show all vm templates available in pooler service, or with the --active flag it will list vms obtained with a pooler service token.'
|
||||
c.example 'Filter the list on centos', 'floaty list centos --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--active', 'Prints information about active vms for a given token'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
service = Service.new(options, config)
|
||||
filter = args[0]
|
||||
url = options.url ||= config['url']
|
||||
token = options.token || config['token']
|
||||
active = options.active
|
||||
|
||||
if active
|
||||
if options.active
|
||||
# list active vms
|
||||
begin
|
||||
running_vms = Utils.get_all_token_vms(verbose, url, token)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
rescue Exception => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ! running_vms.nil?
|
||||
Utils.prettyprint_hosts(running_vms, verbose, url)
|
||||
running_vms = service.list_active(verbose)
|
||||
host = URI.parse(service.url).host
|
||||
if running_vms.empty?
|
||||
puts "You have no running VMs on #{host}"
|
||||
else
|
||||
puts "Your VMs on #{host}:"
|
||||
Utils.pretty_print_hosts(verbose, service, running_vms)
|
||||
end
|
||||
else
|
||||
# list available vms from pooler
|
||||
os_list = Pooler.list(verbose, url, filter)
|
||||
os_list = service.list(verbose, filter)
|
||||
puts os_list
|
||||
end
|
||||
end
|
||||
|
|
@ -148,139 +102,74 @@ class Vmfloaty
|
|||
command :query do |c|
|
||||
c.syntax = 'floaty query hostname [options]'
|
||||
c.summary = 'Get information about a given vm'
|
||||
c.description = 'Given a hostname from vmpooler, vmfloaty with query vmpooler to get various details about the vm.'
|
||||
c.description = 'Given a hostname from the pooler service, vmfloaty with query the service to get various details about the vm.'
|
||||
c.example 'Get information about a sample host', 'floaty query hostname --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
service = Service.new(options, config)
|
||||
hostname = args[0]
|
||||
|
||||
query_req = Pooler.query(verbose, url, hostname)
|
||||
query_req = service.query(verbose, hostname)
|
||||
pp query_req
|
||||
end
|
||||
end
|
||||
|
||||
command :modify do |c|
|
||||
c.syntax = 'floaty modify hostname [options]'
|
||||
c.summary = 'Modify a vms tags, time to live, and disk space'
|
||||
c.description = 'This command makes modifications to the virtual machines state in vmpooler. You can either append tags to the vm, increase how long it stays active for, or increase the amount of disk space.'
|
||||
c.summary = 'Modify a VM\'s tags, time to live, disk space, or reservation reason'
|
||||
c.description = 'This command makes modifications to the virtual machines state in the pooler service. You can either append tags to the vm, increase how long it stays active for, or increase the amount of disk space.'
|
||||
c.example 'Modifies myhost1 to have a TTL of 12 hours and adds a custom tag', 'floaty modify myhost1 --lifetime 12 --url https://myurl --token mytokenstring --tags \'{"tag":"myvalue"}\''
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--lifetime INT', Integer, 'VM TTL (Integer, in hours)'
|
||||
c.option '--disk INT', Integer, 'Increases VM disk space (Integer, in gb)'
|
||||
c.option '--tags STRING', String, 'free-form VM tagging (json)'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--lifetime INT', Integer, 'VM TTL (Integer, in hours) [vmpooler only]'
|
||||
c.option '--disk INT', Integer, 'Increases VM disk space (Integer, in gb) [vmpooler only]'
|
||||
c.option '--tags STRING', String, 'free-form VM tagging (json) [vmpooler only]'
|
||||
c.option '--reason STRING', String, 'VM reservation reason [nspooler only]'
|
||||
c.option '--all', 'Modifies all vms acquired by a token'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
service = Service.new(options, config)
|
||||
hostname = args[0]
|
||||
lifetime = options.lifetime
|
||||
disk = options.disk
|
||||
tags = JSON.parse(options.tags) if options.tags
|
||||
token = options.token || config['token']
|
||||
modify_all = options.all
|
||||
|
||||
running_vms = nil
|
||||
if hostname.nil? and !modify_all
|
||||
STDERR.puts "ERROR: Provide a hostname or specify --all."
|
||||
exit 1
|
||||
end
|
||||
running_vms = modify_all ? service.list_active(verbose) : hostname.split(",")
|
||||
|
||||
tags = options.tags ? JSON.parse(options.tags) : nil
|
||||
modify_hash = {
|
||||
lifetime: options.lifetime,
|
||||
disk: options.disk,
|
||||
tags: tags,
|
||||
reason: options.reason
|
||||
}
|
||||
modify_hash.delete_if { |_, value| value.nil? }
|
||||
|
||||
unless modify_hash.empty?
|
||||
ok = true
|
||||
modified_hash = {}
|
||||
running_vms.each do |vm|
|
||||
begin
|
||||
modified_hash[vm] = service.modify(verbose, vm, modify_hash)
|
||||
rescue ModifyError => e
|
||||
STDERR.puts e
|
||||
ok = false
|
||||
end
|
||||
end
|
||||
if ok
|
||||
if modify_all
|
||||
begin
|
||||
running_vms = Utils.get_all_token_vms(verbose, url, token)
|
||||
rescue Exception => e
|
||||
STDERR.puts e
|
||||
end
|
||||
elsif hostname.include? ","
|
||||
running_vms = hostname.split(",")
|
||||
end
|
||||
|
||||
if lifetime || tags
|
||||
# all vms
|
||||
if !running_vms.nil?
|
||||
begin
|
||||
modify_hash = {}
|
||||
modify_flag = true
|
||||
|
||||
running_vms.each do |vm|
|
||||
modify_hash[vm] = Pooler.modify(verbose, url, vm, token, lifetime, tags)
|
||||
end
|
||||
|
||||
modify_hash.each do |hostname,status|
|
||||
if status == false
|
||||
STDERR.puts "Could not modify #{hostname}."
|
||||
modify_flag = false
|
||||
end
|
||||
end
|
||||
|
||||
if modify_flag
|
||||
puts "Successfully modified all vms. Use `floaty list --active` to see the results."
|
||||
end
|
||||
rescue Exception => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
puts "Successfully modified all VMs."
|
||||
else
|
||||
# Single Vm
|
||||
begin
|
||||
modify_req = Pooler.modify(verbose, url, hostname, token, lifetime, tags)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
if modify_req["ok"]
|
||||
puts "Successfully modified vm #{hostname}."
|
||||
else
|
||||
STDERR.puts "Could not modify given host #{hostname} at #{url}."
|
||||
puts modify_req
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if disk
|
||||
# all vms
|
||||
if !running_vms.nil?
|
||||
begin
|
||||
modify_hash = {}
|
||||
modify_flag = true
|
||||
|
||||
running_vms.each do |vm|
|
||||
modify_hash[vm] = Pooler.disk(verbose, url, vm, token, disk)
|
||||
end
|
||||
|
||||
modify_hash.each do |hostname,status|
|
||||
if status == false
|
||||
STDERR.puts "Could not update disk space on #{hostname}."
|
||||
modify_flag = false
|
||||
end
|
||||
end
|
||||
|
||||
if modify_flag
|
||||
puts "Successfully made request to update disk space on all vms."
|
||||
end
|
||||
rescue Exception => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
# single vm
|
||||
begin
|
||||
disk_req = Pooler.disk(verbose, url, hostname, token, disk)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
if disk_req["ok"]
|
||||
puts "Successfully made request to update disk space of vm #{hostname}."
|
||||
else
|
||||
STDERR.puts "Could not modify given host #{hostname} at #{url}."
|
||||
puts disk_req
|
||||
exit 1
|
||||
puts "Successfully modified VM #{hostname}."
|
||||
end
|
||||
puts "Use `floaty list --active` to see the results."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -289,95 +178,99 @@ class Vmfloaty
|
|||
command :delete do |c|
|
||||
c.syntax = 'floaty delete hostname,hostname2 [options]'
|
||||
c.summary = 'Schedules the deletion of a host or hosts'
|
||||
c.description = 'Given a comma separated list of hostnames, or --all for all vms, vmfloaty makes a request to vmpooler to schedule the deletion of those vms.'
|
||||
c.description = 'Given a comma separated list of hostnames, or --all for all vms, vmfloaty makes a request to the pooler service to schedule the deletion of those vms.'
|
||||
c.example 'Schedules the deletion of a host or hosts', 'floaty delete myhost1,myhost2 --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--all', 'Deletes all vms acquired by a token'
|
||||
c.option '-f', 'Does not prompt user when deleting all vms'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
service = Service.new(options, config)
|
||||
hostnames = args[0]
|
||||
token = options.token || config['token']
|
||||
url = options.url ||= config['url']
|
||||
delete_all = options.all
|
||||
force = options.f
|
||||
|
||||
failures = []
|
||||
successes = []
|
||||
|
||||
if delete_all
|
||||
# get vms with token
|
||||
begin
|
||||
running_vms = Utils.get_all_token_vms(verbose, url, token)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
rescue Exception => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ! running_vms.nil?
|
||||
Utils.prettyprint_hosts(running_vms, verbose, url)
|
||||
# query y/n
|
||||
puts
|
||||
|
||||
if force
|
||||
ans = true
|
||||
running_vms = service.list_active(verbose)
|
||||
if running_vms.empty?
|
||||
STDERR.puts "You have no running VMs."
|
||||
else
|
||||
ans = agree("Delete all VMs associated with token #{token}? [y/N]")
|
||||
Utils.pretty_print_hosts(verbose, service, running_vms)
|
||||
# Confirm deletion
|
||||
puts
|
||||
confirmed = true
|
||||
unless force
|
||||
confirmed = agree('Delete all these VMs? [y/N]')
|
||||
end
|
||||
|
||||
if ans
|
||||
# delete vms
|
||||
puts "Scheduling all vms for for deletion"
|
||||
response = Pooler.delete(verbose, url, running_vms, token)
|
||||
response.each do |host,vals|
|
||||
if vals['ok'] == false
|
||||
STDERR.puts "There was a problem with your request for vm #{host}."
|
||||
STDERR.puts vals
|
||||
if confirmed
|
||||
response = service.delete(verbose, running_vms)
|
||||
response.each do |hostname, result|
|
||||
if result['ok']
|
||||
successes << hostname
|
||||
else
|
||||
failures << hostname
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
exit 0
|
||||
elsif hostnames || args
|
||||
hostnames = hostnames.split(',')
|
||||
results = service.delete(verbose, hostnames)
|
||||
results.each do |hostname, result|
|
||||
if result['ok']
|
||||
successes << hostname
|
||||
else
|
||||
failures << hostname
|
||||
end
|
||||
|
||||
if hostnames.nil?
|
||||
end
|
||||
else
|
||||
STDERR.puts "You did not provide any hosts to delete"
|
||||
exit 1
|
||||
else
|
||||
hosts = hostnames.split(',')
|
||||
begin
|
||||
Pooler.delete(verbose, url, hosts, token)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "Schedulered vmpooler to delete vms #{hosts}."
|
||||
exit 0
|
||||
unless failures.empty?
|
||||
STDERR.puts 'Unable to delete the following VMs:'
|
||||
failures.each do |hostname|
|
||||
STDERR.puts "- #{hostname}"
|
||||
end
|
||||
STDERR.puts 'Check `floaty list --active`; Do you need to specify a different service?'
|
||||
end
|
||||
|
||||
unless successes.empty?
|
||||
puts unless failures.empty?
|
||||
puts 'Scheduled the following VMs for deletion:'
|
||||
successes.each do |hostname|
|
||||
puts "- #{hostname}"
|
||||
end
|
||||
end
|
||||
|
||||
exit 1 unless failures.empty?
|
||||
end
|
||||
end
|
||||
|
||||
command :snapshot do |c|
|
||||
c.syntax = 'floaty snapshot hostname [options]'
|
||||
c.summary = 'Takes a snapshot of a given vm'
|
||||
c.description = 'Will request a snapshot be taken of the given hostname in vmpooler. This command is known to take a while depending on how much load is on vmpooler.'
|
||||
c.description = 'Will request a snapshot be taken of the given hostname in the pooler service. This command is known to take a while depending on how much load is on the pooler service.'
|
||||
c.example 'Takes a snapshot for a given host', 'floaty snapshot myvm.example.com --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
service = Service.new(options, config)
|
||||
hostname = args[0]
|
||||
token = options.token ||= config['token']
|
||||
|
||||
begin
|
||||
snapshot_req = Pooler.snapshot(verbose, url, hostname, token)
|
||||
rescue TokenError => e
|
||||
snapshot_req = service.snapshot(verbose, hostname)
|
||||
rescue TokenError, ModifyError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
|
@ -390,17 +283,17 @@ class Vmfloaty
|
|||
command :revert do |c|
|
||||
c.syntax = 'floaty revert hostname snapshot [options]'
|
||||
c.summary = 'Reverts a vm to a specified snapshot'
|
||||
c.description = 'Given a snapshot SHA, vmfloaty will request a revert to vmpooler to go back to a previous snapshot.'
|
||||
c.description = 'Given a snapshot SHA, vmfloaty will request a revert to the pooler service to go back to a previous snapshot.'
|
||||
c.example 'Reverts to a snapshot for a given host', 'floaty revert myvm.example.com n4eb4kdtp7rwv4x158366vd9jhac8btq --url http://vmpooler.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--snapshot STRING', String, 'SHA of snapshot'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
service = Service.new(options, config)
|
||||
hostname = args[0]
|
||||
token = options.token || config['token']
|
||||
snapshot_sha = args[1] || options.snapshot
|
||||
|
||||
if args[1] && options.snapshot
|
||||
|
|
@ -408,8 +301,8 @@ class Vmfloaty
|
|||
end
|
||||
|
||||
begin
|
||||
revert_req = Pooler.revert(verbose, url, hostname, token, snapshot_sha)
|
||||
rescue TokenError => e
|
||||
revert_req = service.revert(verbose, hostname, snapshot_sha)
|
||||
rescue TokenError, ModifyError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
|
|
@ -420,42 +313,37 @@ class Vmfloaty
|
|||
|
||||
command :status do |c|
|
||||
c.syntax = 'floaty status [options]'
|
||||
c.summary = 'Prints the status of pools in vmpooler'
|
||||
c.description = 'Makes a request to vmpooler to request the information about vm pools and how many are ready to be used, what pools are empty, etc.'
|
||||
c.example 'Gets the current vmpooler status', 'floaty status --url http://vmpooler.example.com'
|
||||
c.summary = 'Prints the status of pools in the pooler service'
|
||||
c.description = 'Makes a request to the pooler service to request the information about vm pools and how many are ready to be used, what pools are empty, etc.'
|
||||
c.example 'Gets the current pooler service status', 'floaty status --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--json', 'Prints status in JSON format'
|
||||
c.action do |args, options|
|
||||
c.action do |_, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
|
||||
status = Pooler.status(verbose, url)
|
||||
message = status['status']['message']
|
||||
pools = status['pools']
|
||||
|
||||
service = Service.new(options, config)
|
||||
if options.json
|
||||
pp status
|
||||
pp service.status(verbose)
|
||||
else
|
||||
Utils.prettyprint_status(status, message, pools, verbose)
|
||||
Utils.pretty_print_status(verbose, service)
|
||||
end
|
||||
|
||||
exit status['status']['ok']
|
||||
end
|
||||
end
|
||||
|
||||
command :summary do |c|
|
||||
c.syntax = 'floaty summary [options]'
|
||||
c.summary = 'Prints a summary of vmpooler'
|
||||
c.description = 'Gives a very detailed summary of information related to vmpooler.'
|
||||
c.example 'Gets the current day summary of vmpooler', 'floaty summary --url http://vmpooler.example.com'
|
||||
c.summary = 'Prints a summary of a pooler service'
|
||||
c.description = 'Gives a very detailed summary of information related to the pooler service.'
|
||||
c.example 'Gets the current day summary of the pooler service', 'floaty summary --url http://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.action do |args, options|
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.action do |_, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
service = Service.new(options, config)
|
||||
|
||||
summary = Pooler.summary(verbose, url)
|
||||
summary = service.summary(verbose)
|
||||
pp summary
|
||||
exit 0
|
||||
end
|
||||
|
|
@ -464,54 +352,45 @@ class Vmfloaty
|
|||
command :token do |c|
|
||||
c.syntax = 'floaty token <get delete status> [options]'
|
||||
c.summary = 'Retrieves or deletes a token or checks token status'
|
||||
c.description = 'This command is used to manage your vmpooler token. Through the various options, you are able to get a new token, delete an existing token, and request a tokens status.'
|
||||
c.description = 'This command is used to manage your pooler service token. Through the various options, you are able to get a new token, delete an existing token, and request a tokens status.'
|
||||
c.example 'Gets a token from the pooler', 'floaty token get'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--user STRING', String, 'User to authenticate with'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
service = Service.new(options, config)
|
||||
action = args.first
|
||||
url = options.url ||= config['url']
|
||||
token = args[1] ||= options.token ||= config['token']
|
||||
user = options.user ||= config['user']
|
||||
|
||||
begin
|
||||
case action
|
||||
when "get"
|
||||
pass = password "Enter your vmpooler password please:", '*'
|
||||
begin
|
||||
token = Auth.get_token(verbose, url, user, pass)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
when 'get'
|
||||
token = service.get_new_token(verbose)
|
||||
puts token
|
||||
exit 0
|
||||
when "delete"
|
||||
pass = password "Enter your vmpooler password please:", '*'
|
||||
begin
|
||||
result = Auth.delete_token(verbose, url, user, pass, token)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
when 'delete'
|
||||
result = service.delete_token(verbose, options.token)
|
||||
puts result
|
||||
exit 0
|
||||
when "status"
|
||||
begin
|
||||
status = Auth.token_status(verbose, url, token)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
when 'status'
|
||||
token_value = options.token
|
||||
if token_value.nil?
|
||||
token_value = args[1]
|
||||
end
|
||||
status = service.token_status(verbose, token_value)
|
||||
puts status
|
||||
exit 0
|
||||
when nil
|
||||
STDERR.puts "No action provided"
|
||||
STDERR.puts 'No action provided'
|
||||
exit 1
|
||||
else
|
||||
STDERR.puts "Unknown action: #{action}"
|
||||
exit 1
|
||||
end
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
exit 1
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -521,16 +400,15 @@ class Vmfloaty
|
|||
c.description = 'This command simply will grab a vm template that was requested, and then ssh the user into the machine all at once.'
|
||||
c.example 'SSHs into a centos vm', 'floaty ssh centos7 --url https://vmpooler.example.com'
|
||||
c.option '--verbose', 'Enables verbose output'
|
||||
c.option '--url STRING', String, 'URL of vmpooler'
|
||||
c.option '--service STRING', String, 'Configured pooler service name'
|
||||
c.option '--url STRING', String, 'URL of pooler service'
|
||||
c.option '--user STRING', String, 'User to authenticate with'
|
||||
c.option '--token STRING', String, 'Token for vmpooler'
|
||||
c.option '--token STRING', String, 'Token for pooler service'
|
||||
c.option '--notoken', 'Makes a request without a token'
|
||||
c.action do |args, options|
|
||||
verbose = options.verbose || config['verbose']
|
||||
url = options.url ||= config['url']
|
||||
token = options.token ||= config['token']
|
||||
user = options.user ||= config['user']
|
||||
no_token = options.notoken
|
||||
service = Service.new(options, config)
|
||||
use_token = !options.notoken
|
||||
|
||||
if args.empty?
|
||||
STDERR.puts "No operating systems provided to obtain. See `floaty ssh --help` for more information on how to get VMs."
|
||||
|
|
@ -539,25 +417,11 @@ class Vmfloaty
|
|||
|
||||
host_os = args.first
|
||||
|
||||
if !no_token && !token
|
||||
puts "No token found. Retrieving a token..."
|
||||
if !user
|
||||
STDERR.puts "You did not provide a user to authenticate to vmpooler with"
|
||||
exit 1
|
||||
end
|
||||
pass = password "Enter your vmpooler password please:", '*'
|
||||
begin
|
||||
token = Auth.get_token(verbose, url, user, pass)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
STDERR.puts 'Could not get token...requesting vm without a token anyway...'
|
||||
else
|
||||
puts "\nToken retrieved!"
|
||||
puts token
|
||||
end
|
||||
if args.length > 1
|
||||
STDERR.puts "Can't ssh to multiple hosts; Using #{host_os} only..."
|
||||
end
|
||||
|
||||
Ssh.ssh(verbose, host_os, token, url)
|
||||
service.ssh(verbose, host_os, use_token)
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
|
@ -574,7 +438,7 @@ class Vmfloaty
|
|||
EOF
|
||||
c.example 'Gets path to bash tab completion script', 'floaty completion --shell bash'
|
||||
c.option '--shell STRING', String, 'Shell to request completion script for'
|
||||
c.action do |args, options|
|
||||
c.action do |_, options|
|
||||
shell = (options.shell || 'bash').downcase.strip
|
||||
completion_file = File.expand_path(File.join('..', '..', 'extras', 'completions', "floaty.#{shell}"), __FILE__)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,3 +15,9 @@ class MissingParamError < StandardError
|
|||
super
|
||||
end
|
||||
end
|
||||
|
||||
class ModifyError < StandardError
|
||||
def initialize(msg="Could not modify VM")
|
||||
super
|
||||
end
|
||||
end
|
||||
|
|
|
|||
135
lib/vmfloaty/nonstandard_pooler.rb
Normal file
135
lib/vmfloaty/nonstandard_pooler.rb
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
require 'vmfloaty/errors'
|
||||
require 'vmfloaty/http'
|
||||
require 'faraday'
|
||||
require 'json'
|
||||
|
||||
class NonstandardPooler
|
||||
def self.list(verbose, url, os_filter = nil)
|
||||
conn = Http.get_conn(verbose, url)
|
||||
|
||||
response = conn.get 'status'
|
||||
response_body = JSON.parse(response.body)
|
||||
os_list = response_body.keys.sort
|
||||
os_list.delete 'ok'
|
||||
|
||||
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
|
||||
end
|
||||
|
||||
def self.list_active(verbose, url, token)
|
||||
status = Auth.token_status(verbose, url, token)
|
||||
status['reserved_hosts'] || []
|
||||
end
|
||||
|
||||
def self.retrieve(verbose, os_type, token, url)
|
||||
conn = Http.get_conn(verbose, url)
|
||||
conn.headers['X-AUTH-TOKEN'] = token if token
|
||||
|
||||
os_string = ''
|
||||
os_type.each do |os, num|
|
||||
num.times do |_i|
|
||||
os_string << os + '+'
|
||||
end
|
||||
end
|
||||
|
||||
os_string = os_string.chomp('+')
|
||||
|
||||
if os_string.empty?
|
||||
raise MissingParamError, 'No operating systems provided to obtain.'
|
||||
end
|
||||
|
||||
response = conn.post "host/#{os_string}"
|
||||
|
||||
res_body = JSON.parse(response.body)
|
||||
|
||||
if res_body['ok']
|
||||
res_body
|
||||
elsif response.status == 401
|
||||
raise AuthError, "HTTP #{response.status}: The token provided could not authenticate to the pooler.\n#{res_body}"
|
||||
else
|
||||
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/host/#{os_string}. #{res_body}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.modify(verbose, url, hostname, token, modify_hash)
|
||||
if token.nil?
|
||||
raise TokenError, 'Token provided was nil; Request cannot be made to modify VM'
|
||||
end
|
||||
|
||||
modify_hash.each do |key, value|
|
||||
unless [:reason, :reserved_for_reason].include? key
|
||||
raise ModifyError, "Configured service type does not support modification of #{key}"
|
||||
end
|
||||
end
|
||||
|
||||
if modify_hash[:reason]
|
||||
# "reason" is easier to type than "reserved_for_reason", but nspooler needs the latter
|
||||
modify_hash[:reserved_for_reason] = modify_hash.delete :reason
|
||||
end
|
||||
|
||||
conn = Http.get_conn(verbose, url)
|
||||
conn.headers['X-AUTH-TOKEN'] = token
|
||||
|
||||
response = conn.put do |req|
|
||||
req.url "host/#{hostname}"
|
||||
req.body = modify_hash.to_json
|
||||
end
|
||||
|
||||
response.body.empty? ? {} : JSON.parse(response.body)
|
||||
end
|
||||
|
||||
def self.disk(verbose, url, hostname, token, disk)
|
||||
raise ModifyError, 'Configured service type does not support modification of disk space'
|
||||
end
|
||||
|
||||
def self.snapshot(verbose, url, hostname, token)
|
||||
raise ModifyError, 'Configured service type does not support snapshots'
|
||||
end
|
||||
|
||||
def self.revert(verbose, url, hostname, token, snapshot_sha)
|
||||
raise ModifyError, 'Configured service type does not support snapshots'
|
||||
end
|
||||
|
||||
def self.delete(verbose, url, hosts, token)
|
||||
if token.nil?
|
||||
raise TokenError, 'Token provided was nil; Request cannot be made to delete VM'
|
||||
end
|
||||
|
||||
conn = Http.get_conn(verbose, url)
|
||||
|
||||
conn.headers['X-AUTH-TOKEN'] = token if token
|
||||
|
||||
response_body = {}
|
||||
|
||||
unless hosts.is_a? Array
|
||||
hosts = hosts.split(',')
|
||||
end
|
||||
hosts.each do |host|
|
||||
response = conn.delete "host/#{host}"
|
||||
res_body = JSON.parse(response.body)
|
||||
response_body[host] = res_body
|
||||
end
|
||||
|
||||
response_body
|
||||
end
|
||||
|
||||
def self.status(verbose, url)
|
||||
conn = Http.get_conn(verbose, url)
|
||||
|
||||
response = conn.get '/status'
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
def self.summary(verbose, url)
|
||||
conn = Http.get_conn(verbose, url)
|
||||
|
||||
response = conn.get '/summary'
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
def self.query(verbose, url, hostname)
|
||||
conn = Http.get_conn(verbose, url)
|
||||
|
||||
response = conn.get "host/#{hostname}"
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
end
|
||||
|
|
@ -19,6 +19,15 @@ class Pooler
|
|||
hosts
|
||||
end
|
||||
|
||||
def self.list_active(verbose, url, token)
|
||||
status = Auth.token_status(verbose, url, token)
|
||||
vms = []
|
||||
if status[token] && status[token]['vms']
|
||||
vms = status[token]['vms']['running']
|
||||
end
|
||||
vms
|
||||
end
|
||||
|
||||
def self.retrieve(verbose, os_type, token, url)
|
||||
# NOTE:
|
||||
# Developers can use `Utils.generate_os_hash` to
|
||||
|
|
@ -54,25 +63,28 @@ class Pooler
|
|||
end
|
||||
end
|
||||
|
||||
def self.modify(verbose, url, hostname, token, lifetime, tags)
|
||||
def self.modify(verbose, url, hostname, token, modify_hash)
|
||||
if token.nil?
|
||||
raise TokenError, "Token provided was nil. Request cannot be made to modify vm"
|
||||
end
|
||||
|
||||
modify_body = {}
|
||||
if lifetime
|
||||
modify_body['lifetime'] = lifetime
|
||||
modify_hash.keys.each do |key|
|
||||
unless [:tags, :lifetime, :disk].include? key
|
||||
raise ModifyError, "Configured service type does not support modification of #{key}."
|
||||
end
|
||||
if tags
|
||||
modify_body['tags'] = tags
|
||||
end
|
||||
|
||||
conn = Http.get_conn(verbose, url)
|
||||
conn.headers['X-AUTH-TOKEN'] = token
|
||||
|
||||
if modify_hash['disk']
|
||||
disk(verbose, url, hostname, token, modify_hash['disk'])
|
||||
modify_hash.delete 'disk'
|
||||
end
|
||||
|
||||
response = conn.put do |req|
|
||||
req.url "vm/#{hostname}"
|
||||
req.body = modify_body.to_json
|
||||
req.body = modify_hash.to_json
|
||||
end
|
||||
|
||||
res_body = JSON.parse(response.body)
|
||||
|
|
|
|||
133
lib/vmfloaty/service.rb
Normal file
133
lib/vmfloaty/service.rb
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
require 'commander/user_interaction'
|
||||
require 'commander/command'
|
||||
require 'vmfloaty/utils'
|
||||
require 'vmfloaty/ssh'
|
||||
|
||||
class Service
|
||||
|
||||
attr_reader :config
|
||||
|
||||
def initialize(options, config_hash = {})
|
||||
options ||= Commander::Command::Options.new
|
||||
@config = Utils.get_service_config config_hash, options
|
||||
@service_object = Utils.get_service_object @config['type']
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
if @service_object.respond_to? m
|
||||
@service_object.send(m, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def url
|
||||
@config['url']
|
||||
end
|
||||
|
||||
def type
|
||||
@service_object.name
|
||||
end
|
||||
|
||||
def user
|
||||
unless @config['user']
|
||||
puts "Enter your pooler service username:"
|
||||
@config['user'] = STDIN.gets.chomp
|
||||
end
|
||||
@config['user']
|
||||
end
|
||||
|
||||
def token
|
||||
unless @config['token']
|
||||
puts "No token found. Retrieving a token..."
|
||||
@config['token'] = get_new_token(nil)
|
||||
end
|
||||
@config['token']
|
||||
end
|
||||
|
||||
def get_new_token(verbose)
|
||||
username = user
|
||||
pass = Commander::UI::password "Enter your pooler service password:", '*'
|
||||
Auth.get_token(verbose, url, username, pass)
|
||||
end
|
||||
|
||||
def delete_token(verbose, token_value = @config['token'])
|
||||
username = user
|
||||
pass = Commander::UI::password "Enter your pooler service password:", '*'
|
||||
Auth.delete_token(verbose, url, username, pass, token_value)
|
||||
end
|
||||
|
||||
def token_status(verbose, token_value)
|
||||
token_value ||= @config['token']
|
||||
Auth.token_status(verbose, url, token_value)
|
||||
end
|
||||
|
||||
def list(verbose, os_filter = nil)
|
||||
@service_object.list verbose, url, os_filter
|
||||
end
|
||||
|
||||
def list_active(verbose)
|
||||
@service_object.list_active verbose, url, token
|
||||
end
|
||||
|
||||
def retrieve(verbose, os_types, use_token = true)
|
||||
puts 'Requesting a vm without a token...' unless use_token
|
||||
token_value = use_token ? token : nil
|
||||
@service_object.retrieve verbose, os_types, token_value, url
|
||||
end
|
||||
|
||||
def ssh(verbose, host_os, use_token = true)
|
||||
token_value = nil
|
||||
if use_token
|
||||
begin
|
||||
token_value = token || get_new_token(verbose)
|
||||
rescue TokenError => e
|
||||
STDERR.puts e
|
||||
STDERR.puts 'Could not get token... requesting vm without a token anyway...'
|
||||
end
|
||||
end
|
||||
Ssh.ssh(verbose, host_os, token_value, url)
|
||||
end
|
||||
|
||||
def pretty_print_running(verbose, hostnames = [])
|
||||
if hostnames.empty?
|
||||
puts "You have no running VMs."
|
||||
else
|
||||
puts "Running VMs:"
|
||||
@service_object.pretty_print_hosts(verbose, hostnames, url)
|
||||
end
|
||||
end
|
||||
|
||||
def query(verbose, hostname)
|
||||
@service_object.query verbose, url, hostname
|
||||
end
|
||||
|
||||
def modify(verbose, hostname, modify_hash)
|
||||
@service_object.modify verbose, url, hostname, token, modify_hash
|
||||
end
|
||||
|
||||
def delete(verbose, hosts)
|
||||
@service_object.delete verbose, url, hosts, token
|
||||
end
|
||||
|
||||
def status(verbose)
|
||||
@service_object.status verbose, url
|
||||
end
|
||||
|
||||
def summary(verbose)
|
||||
@service_object.summary verbose, url
|
||||
end
|
||||
|
||||
def snapshot(verbose, hostname)
|
||||
@service_object.snapshot verbose, url, hostname, token
|
||||
end
|
||||
|
||||
def revert(verbose, hostname, snapshot_sha)
|
||||
@service_object.revert verbose, url, hostname, token, snapshot_sha
|
||||
end
|
||||
|
||||
def disk(verbose, hostname, disk)
|
||||
@service_object.disk(verbose, url, hostname, token, disk)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,27 +1,62 @@
|
|||
|
||||
require 'vmfloaty/pooler'
|
||||
require 'vmfloaty/nonstandard_pooler'
|
||||
|
||||
class Utils
|
||||
# TODO: Takes the json response body from an HTTP GET
|
||||
# request and "pretty prints" it
|
||||
def self.format_hosts(hostname_hash)
|
||||
host_hash = {}
|
||||
def self.format_hosts(response_body)
|
||||
# vmpooler response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
|
||||
# {
|
||||
# "ok": true,
|
||||
# "domain": "delivery.mycompany.net",
|
||||
# "ubuntu-1610-x86_64": {
|
||||
# "hostname": ["gdoy8q3nckuob0i", "ctnktsd0u11p9tm"]
|
||||
# },
|
||||
# "centos-7-x86_64": {
|
||||
# "hostname": "dlgietfmgeegry2"
|
||||
# }
|
||||
# }
|
||||
|
||||
hostname_hash.delete("ok")
|
||||
domain = hostname_hash["domain"]
|
||||
hostname_hash.each do |type, hosts|
|
||||
if type != "domain"
|
||||
if hosts["hostname"].kind_of?(Array)
|
||||
hosts["hostname"].map!{|host| host + "." + domain }
|
||||
# nonstandard pooler response body example when `floaty get` arguments are `solaris-11-sparc=2 ubuntu-16.04-power8`:
|
||||
# {
|
||||
# "ok": true,
|
||||
# "solaris-10-sparc": {
|
||||
# "hostname": ["sol10-10.delivery.mycompany.net", "sol10-11.delivery.mycompany.net"]
|
||||
# },
|
||||
# "ubuntu-16.04-power8": {
|
||||
# "hostname": "power8-ubuntu1604-6.delivery.mycompany.net"
|
||||
# }
|
||||
# }
|
||||
|
||||
unless response_body.delete('ok')
|
||||
raise ArgumentError, "Bad GET response passed to format_hosts: #{response_body.to_json}"
|
||||
end
|
||||
|
||||
hostnames = []
|
||||
|
||||
# vmpooler reports the domain separately from the hostname
|
||||
domain = response_body.delete('domain')
|
||||
|
||||
if domain
|
||||
# vmpooler output
|
||||
response_body.each do |os, hosts|
|
||||
if hosts['hostname'].kind_of?(Array)
|
||||
hosts['hostname'].map!{ |host| hostnames << host + "." + domain + " (#{os})"}
|
||||
else
|
||||
hosts["hostname"] = hosts["hostname"] + "." + domain
|
||||
hostnames << hosts["hostname"] + ".#{domain} (#{os})"
|
||||
end
|
||||
end
|
||||
else
|
||||
response_body.each do |os, hosts|
|
||||
if hosts['hostname'].kind_of?(Array)
|
||||
hosts['hostname'].map!{ |host| hostnames << host + " (#{os})" }
|
||||
else
|
||||
hostnames << hosts['hostname'] + " (#{os})"
|
||||
end
|
||||
|
||||
host_hash[type] = hosts["hostname"]
|
||||
end
|
||||
end
|
||||
|
||||
host_hash.to_json
|
||||
hostnames.map { |hostname| puts "- #{hostname}" }
|
||||
end
|
||||
|
||||
def self.generate_os_hash(os_args)
|
||||
|
|
@ -46,55 +81,48 @@ class Utils
|
|||
os_types
|
||||
end
|
||||
|
||||
def self.get_vm_info(hosts, verbose, url)
|
||||
vms = {}
|
||||
hosts.each do |host|
|
||||
vm_info = Pooler.query(verbose, url, host)
|
||||
if vm_info['ok']
|
||||
vms[host] = {}
|
||||
vms[host]['domain'] = vm_info[host]['domain']
|
||||
vms[host]['template'] = vm_info[host]['template']
|
||||
vms[host]['lifetime'] = vm_info[host]['lifetime']
|
||||
vms[host]['running'] = vm_info[host]['running']
|
||||
vms[host]['tags'] = vm_info[host]['tags']
|
||||
end
|
||||
end
|
||||
vms
|
||||
end
|
||||
def self.pretty_print_hosts(verbose, service, hostnames = [])
|
||||
hostnames = [hostnames] unless hostnames.is_a? Array
|
||||
hostnames.each do |hostname|
|
||||
begin
|
||||
response = service.query(verbose, hostname)
|
||||
host_data = response[hostname]
|
||||
|
||||
def self.prettyprint_hosts(hosts, verbose, url)
|
||||
puts "Running VMs:"
|
||||
vm_info = get_vm_info(hosts, verbose, url)
|
||||
vm_info.each do |vm,info|
|
||||
domain = info['domain']
|
||||
template = info['template']
|
||||
lifetime = info['lifetime']
|
||||
running = info['running']
|
||||
tags = info['tags'] || {}
|
||||
|
||||
tag_pairs = tags.map {|key,value| "#{key}: #{value}" }
|
||||
duration = "#{running}/#{lifetime} hours"
|
||||
metadata = [template, duration, *tag_pairs]
|
||||
|
||||
puts "- #{vm}.#{domain} (#{metadata.join(", ")})"
|
||||
case service.type
|
||||
when 'Pooler'
|
||||
tag_pairs = []
|
||||
unless host_data['tags'].nil?
|
||||
tag_pairs = host_data['tags'].map {|key, value| "#{key}: #{value}"}
|
||||
end
|
||||
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
||||
metadata = [host_data['template'], duration, *tag_pairs]
|
||||
puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(", ")})"
|
||||
when 'NonstandardPooler'
|
||||
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
||||
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
||||
unless host_data['reserved_for_reason'].empty?
|
||||
line += ", reason: #{host_data['reserved_for_reason']}"
|
||||
end
|
||||
line += ')'
|
||||
puts line
|
||||
else
|
||||
raise "Invalid service type #{service.type}"
|
||||
end
|
||||
rescue => e
|
||||
STDERR.puts("Something went wrong while trying to gather information on #{hostname}:")
|
||||
STDERR.puts(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_all_token_vms(verbose, url, token)
|
||||
# get vms with token
|
||||
status = Auth.token_status(verbose, url, token)
|
||||
def self.pretty_print_status(verbose, service)
|
||||
status_response = service.status(verbose)
|
||||
|
||||
vms = status[token]['vms']
|
||||
if vms.nil?
|
||||
raise "You have no running vms"
|
||||
end
|
||||
|
||||
running_vms = vms['running']
|
||||
running_vms
|
||||
end
|
||||
|
||||
def self.prettyprint_status(status, message, pools, verbose)
|
||||
pools.select! {|name,pool| pool['ready'] < pool['max']} if ! verbose
|
||||
case service.type
|
||||
when 'Pooler'
|
||||
message = status_response['status']['message']
|
||||
pools = status_response['pools']
|
||||
pools.select! {|_, pool| pool['ready'] < pool['max']} unless verbose
|
||||
|
||||
width = pools.keys.map(&:length).max
|
||||
pools.each do |name, pool|
|
||||
|
|
@ -109,9 +137,28 @@ class Utils
|
|||
puts "#{name.ljust(width)} #{e.red}"
|
||||
end
|
||||
end
|
||||
puts message.colorize(status_response['status']['ok'] ? :default : :red)
|
||||
when 'NonstandardPooler'
|
||||
pools = status_response
|
||||
pools.delete 'ok'
|
||||
pools.select! {|_, pool| pool['available_hosts'] < pool['total_hosts']} unless verbose
|
||||
|
||||
puts
|
||||
puts message.colorize(status['status']['ok'] ? :default : :red)
|
||||
width = pools.keys.map(&:length).max
|
||||
pools.each do |name, pool|
|
||||
begin
|
||||
max = pool['total_hosts']
|
||||
ready = pool['available_hosts']
|
||||
pending = pool['pending'] || 0 # not available for nspooler
|
||||
missing = max - ready - pending
|
||||
char = 'o'
|
||||
puts "#{name.ljust(width)} #{(char*ready).green}#{(char*pending).yellow}#{(char*missing).red}"
|
||||
rescue => e
|
||||
puts "#{name.ljust(width)} #{e.red}"
|
||||
end
|
||||
end
|
||||
else
|
||||
raise "Invalid service type #{service.type}"
|
||||
end
|
||||
end
|
||||
|
||||
# Adapted from ActiveSupport
|
||||
|
|
@ -121,4 +168,47 @@ class Utils
|
|||
|
||||
str.gsub(/^[ \t]{#{min_indent_size}}/, '')
|
||||
end
|
||||
|
||||
def self.get_service_object(type = '')
|
||||
nspooler_strings = ['ns', 'nspooler', 'nonstandard', 'nonstandard_pooler']
|
||||
if nspooler_strings.include? type.downcase
|
||||
NonstandardPooler
|
||||
else
|
||||
Pooler
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_service_config(config, options)
|
||||
# The top-level url, user, and token values in the config file are treated as defaults
|
||||
service_config = {
|
||||
'url' => config['url'],
|
||||
'user' => config['user'],
|
||||
'token' => config['token'],
|
||||
'type' => config['type'] || 'vmpooler'
|
||||
}
|
||||
|
||||
if config['services']
|
||||
if options.service.nil?
|
||||
# If the user did not specify a service name at the command line, but configured services do exist,
|
||||
# use the first configured service in the list by default.
|
||||
_, values = config['services'].first
|
||||
service_config.merge! values
|
||||
else
|
||||
# If the user provided a service name at the command line, use that service if posible, or fail
|
||||
if config['services'][options.service]
|
||||
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
||||
service_config.merge! config['services'][options.service]
|
||||
else
|
||||
raise ArgumentError, "Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Prioritize an explicitly specified url, user, or token if the user provided one
|
||||
service_config['url'] = options.url unless options.url.nil?
|
||||
service_config['token'] = options.token unless options.token.nil?
|
||||
service_config['user'] = options.user unless options.user.nil?
|
||||
|
||||
service_config
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
require 'vmfloaty'
|
||||
require 'webmock/rspec'
|
||||
|
||||
# Mock Commander Options object to allow pre-population with values
|
||||
class MockOptions < Commander::Command::Options
|
||||
def initialize(values = {})
|
||||
@table = values
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.color = true
|
||||
config.tty = true
|
||||
|
|
|
|||
325
spec/vmfloaty/nonstandard_pooler_spec.rb
Normal file
325
spec/vmfloaty/nonstandard_pooler_spec.rb
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
require 'spec_helper'
|
||||
require 'vmfloaty/utils'
|
||||
require 'vmfloaty/errors'
|
||||
require 'vmfloaty/nonstandard_pooler'
|
||||
|
||||
describe NonstandardPooler do
|
||||
before :each do
|
||||
@nspooler_url = 'https://nspooler.example.com'
|
||||
@post_request_headers = {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
||||
'User-Agent' => 'Faraday v0.9.2',
|
||||
'X-Auth-Token' => 'token-value'
|
||||
}
|
||||
@get_request_headers = {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
||||
'User-Agent' => 'Faraday v0.9.2',
|
||||
'X-Auth-Token' => 'token-value'
|
||||
}
|
||||
@get_request_headers_notoken = @get_request_headers.tap do |headers|
|
||||
headers.delete('X-Auth-Token')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#list' do
|
||||
before :each do
|
||||
@status_response_body = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"solaris-10-sparc": {
|
||||
"total_hosts": 11,
|
||||
"available_hosts": 11
|
||||
},
|
||||
"ubuntu-16.04-power8": {
|
||||
"total_hosts": 10,
|
||||
"available_hosts": 10
|
||||
},
|
||||
"aix-7.2-power": {
|
||||
"total_hosts": 5,
|
||||
"available_hosts": 4
|
||||
}
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'returns an array with operating systems from the pooler' do
|
||||
stub_request(:get, "#{@nspooler_url}/status")
|
||||
.to_return(status: 200, body: @status_response_body, headers: {})
|
||||
|
||||
list = NonstandardPooler.list(false, @nspooler_url, nil)
|
||||
expect(list).to be_an_instance_of Array
|
||||
end
|
||||
|
||||
it 'filters operating systems based on the filter param' do
|
||||
stub_request(:get, "#{@nspooler_url}/status")
|
||||
.to_return(status: 200, body: @status_response_body, headers: {})
|
||||
|
||||
list = NonstandardPooler.list(false, @nspooler_url, 'aix')
|
||||
expect(list).to be_an_instance_of Array
|
||||
expect(list.size).to equal 1
|
||||
end
|
||||
|
||||
it 'returns nothing if the filter does not match' do
|
||||
stub_request(:get, "#{@nspooler_url}/status")
|
||||
.to_return(status: 199, body: @status_response_body, headers: {})
|
||||
|
||||
list = NonstandardPooler.list(false, @nspooler_url, 'windows')
|
||||
expect(list).to be_an_instance_of Array
|
||||
expect(list.size).to equal 0
|
||||
end
|
||||
end
|
||||
|
||||
describe '#list_active' do
|
||||
before :each do
|
||||
@token_status_body_active = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"user": "first.last",
|
||||
"created": "2017-09-18 01:25:41 +0000",
|
||||
"last_accessed": "2017-09-21 19:46:25 +0000",
|
||||
"reserved_hosts": ["sol10-9", "sol10-11"]
|
||||
}
|
||||
BODY
|
||||
@token_status_body_empty = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"user": "first.last",
|
||||
"created": "2017-09-18 01:25:41 +0000",
|
||||
"last_accessed": "2017-09-21 19:46:25 +0000",
|
||||
"reserved_hosts": []
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'prints an output of fqdn, template, and duration' do
|
||||
allow(Auth).to receive(:token_status)
|
||||
.with(false, @nspooler_url, 'token-value')
|
||||
.and_return(JSON.parse(@token_status_body_active))
|
||||
|
||||
list = NonstandardPooler.list_active(false, @nspooler_url, 'token-value')
|
||||
expect(list).to eql ['sol10-9', 'sol10-11']
|
||||
end
|
||||
end
|
||||
|
||||
describe '#retrieve' do
|
||||
before :each do
|
||||
@retrieve_response_body_single = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"solaris-11-sparc": {
|
||||
"hostname": "sol11-4.delivery.puppetlabs.net"
|
||||
}
|
||||
}
|
||||
BODY
|
||||
@retrieve_response_body_many = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"solaris-10-sparc": {
|
||||
"hostname": [
|
||||
"sol10-9.delivery.puppetlabs.net",
|
||||
"sol10-10.delivery.puppetlabs.net"
|
||||
]
|
||||
},
|
||||
"aix-7.1-power": {
|
||||
"hostname": "pe-aix-71-ci-acceptance.delivery.puppetlabs.net"
|
||||
}
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'raises an AuthError if the token is invalid' do
|
||||
stub_request(:post, "#{@nspooler_url}/host/solaris-11-sparc")
|
||||
.with(headers: @post_request_headers)
|
||||
.to_return(status: 401, body: '{"ok":false,"reason": "token: token-value does not exist"}', headers: {})
|
||||
|
||||
vm_hash = { 'solaris-11-sparc' => 1 }
|
||||
expect { NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url) }.to raise_error(AuthError)
|
||||
end
|
||||
|
||||
it 'retrieves a single vm with a token' do
|
||||
stub_request(:post, "#{@nspooler_url}/host/solaris-11-sparc")
|
||||
.with(headers: @post_request_headers)
|
||||
.to_return(status: 200, body: @retrieve_response_body_single, headers: {})
|
||||
|
||||
vm_hash = { 'solaris-11-sparc' => 1 }
|
||||
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
|
||||
expect(vm_req).to be_an_instance_of Hash
|
||||
expect(vm_req['ok']).to equal true
|
||||
expect(vm_req['solaris-11-sparc']['hostname']).to eq 'sol11-4.delivery.puppetlabs.net'
|
||||
end
|
||||
|
||||
it 'retrieves a multiple vms with a token' do
|
||||
stub_request(:post,"#{@nspooler_url}/host/aix-7.1-power+solaris-10-sparc+solaris-10-sparc")
|
||||
.with(headers: @post_request_headers)
|
||||
.to_return(status: 200, body: @retrieve_response_body_many, headers: {})
|
||||
|
||||
vm_hash = { 'aix-7.1-power' => 1, 'solaris-10-sparc' => 2 }
|
||||
vm_req = NonstandardPooler.retrieve(false, vm_hash, 'token-value', @nspooler_url)
|
||||
expect(vm_req).to be_an_instance_of Hash
|
||||
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 eq ['sol10-9.delivery.puppetlabs.net', 'sol10-10.delivery.puppetlabs.net']
|
||||
expect(vm_req['aix-7.1-power']['hostname']).to eq 'pe-aix-71-ci-acceptance.delivery.puppetlabs.net'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#modify' do
|
||||
before :each do
|
||||
@modify_response_body_success = '{"ok":true}'
|
||||
end
|
||||
|
||||
it 'raises an error if the user tries to modify an unsupported attribute' do
|
||||
stub_request(:put, "https://nspooler.example.com/host/myfakehost").
|
||||
with(body: {"{}"=>true},
|
||||
headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'token-value'}).
|
||||
to_return(status: 200, body: "", headers: {})
|
||||
details = { lifetime: 12 }
|
||||
expect { NonstandardPooler.modify(false, @nspooler_url, 'myfakehost', 'token-value', details) }
|
||||
.to raise_error(ModifyError)
|
||||
end
|
||||
|
||||
it 'modifies the reason of a vm' do
|
||||
modify_request_body = { '{"reserved_for_reason":"testing"}' => true }
|
||||
stub_request(:put, "#{@nspooler_url}/host/myfakehost")
|
||||
.with(body: modify_request_body,
|
||||
headers: @post_request_headers)
|
||||
.to_return(status: 200, body: '{"ok": true}', headers: {})
|
||||
|
||||
modify_hash = { reason: "testing" }
|
||||
modify_req = NonstandardPooler.modify(false, @nspooler_url, 'myfakehost', 'token-value', modify_hash)
|
||||
expect(modify_req['ok']).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#status' do
|
||||
before :each do
|
||||
@status_response_body = '{"capacity":{"current":716,"total":717,"percent": 99.9},"status":{"ok":true,"message":"Battle station fully armed and operational."}}'
|
||||
# TODO: make this report stuff like 'broken'
|
||||
@status_response_body = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"solaris-10-sparc": {
|
||||
"total_hosts": 11,
|
||||
"available_hosts": 10
|
||||
},
|
||||
"ubuntu-16.04-power8": {
|
||||
"total_hosts": 10,
|
||||
"available_hosts": 10
|
||||
},
|
||||
"aix-7.2-power": {
|
||||
"total_hosts": 5,
|
||||
"available_hosts": 4
|
||||
}
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'prints the status' do
|
||||
stub_request(:get, "#{@nspooler_url}/status")
|
||||
.with(headers: @get_request_headers)
|
||||
.to_return(status: 200, body: @status_response_body, headers: {})
|
||||
|
||||
status = NonstandardPooler.status(false, @nspooler_url)
|
||||
expect(status).to be_an_instance_of Hash
|
||||
end
|
||||
end
|
||||
|
||||
describe '#summary' do
|
||||
before :each do
|
||||
@status_response_body = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"total": 57,
|
||||
"available": 39,
|
||||
"in_use": 16,
|
||||
"resetting": 2,
|
||||
"broken": 0
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'prints the summary' do
|
||||
stub_request(:get, "#{@nspooler_url}/summary")
|
||||
.with(headers: @get_request_headers)
|
||||
.to_return(status: 200, body: @status_response_body, headers: {})
|
||||
|
||||
summary = NonstandardPooler.summary(false, @nspooler_url)
|
||||
expect(summary).to be_an_instance_of Hash
|
||||
end
|
||||
end
|
||||
|
||||
describe '#query' do
|
||||
before :each do
|
||||
@query_response_body = <<-BODY
|
||||
{
|
||||
"ok": true,
|
||||
"sol10-11": {
|
||||
"fqdn": "sol10-11.delivery.puppetlabs.net",
|
||||
"os_triple": "solaris-10-sparc",
|
||||
"reserved_by_user": "first.last",
|
||||
"reserved_for_reason": "testing",
|
||||
"hours_left_on_reservation": 29.12
|
||||
}
|
||||
}
|
||||
BODY
|
||||
end
|
||||
|
||||
it 'makes a query about a vm' do
|
||||
stub_request(:get, "#{@nspooler_url}/host/sol10-11")
|
||||
.with(headers: @get_request_headers_notoken)
|
||||
.to_return(status: 200, body: @query_response_body, headers: {})
|
||||
|
||||
query_req = NonstandardPooler.query(false, @nspooler_url, 'sol10-11')
|
||||
expect(query_req).to be_an_instance_of Hash
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete' do
|
||||
before :each do
|
||||
@delete_response_success = '{"ok": true}'
|
||||
@delete_response_failure = '{"ok": false, "failure": "ERROR: fakehost does not exist"}'
|
||||
end
|
||||
|
||||
it 'deletes a single existing vm' do
|
||||
stub_request(:delete, "#{@nspooler_url}/host/sol11-7")
|
||||
.with(headers: @post_request_headers)
|
||||
.to_return(status: 200, body: @delete_response_success, headers: {})
|
||||
|
||||
request = NonstandardPooler.delete(false, @nspooler_url, 'sol11-7', 'token-value')
|
||||
expect(request['sol11-7']['ok']).to be true
|
||||
end
|
||||
|
||||
it 'does not delete a nonexistant vm' do
|
||||
stub_request(:delete, "#{@nspooler_url}/host/fakehost")
|
||||
.with(headers: @post_request_headers)
|
||||
.to_return(status: 401, body: @delete_response_failure, headers: {})
|
||||
|
||||
request = NonstandardPooler.delete(false, @nspooler_url, 'fakehost', 'token-value')
|
||||
expect(request['fakehost']['ok']).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#snapshot' do
|
||||
it 'logs an error explaining that snapshots are not supported' do
|
||||
expect { NonstandardPooler.snapshot(false, @nspooler_url, 'myfakehost', 'token-value') }
|
||||
.to raise_error(ModifyError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#revert' do
|
||||
it 'logs an error explaining that snapshots are not supported' do
|
||||
expect { NonstandardPooler.revert(false, @nspooler_url, 'myfakehost', 'token-value', 'snapshot-sha') }
|
||||
.to raise_error(ModifyError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#disk' do
|
||||
it 'logs an error explaining that disk modification is not supported' do
|
||||
expect { NonstandardPooler.disk(false, @nspooler_url, 'myfakehost', 'token-value', 'diskname') }
|
||||
.to raise_error(ModifyError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -91,16 +91,17 @@ describe Pooler do
|
|||
end
|
||||
|
||||
it "raises a TokenError if token provided is nil" do
|
||||
expect{ Pooler.modify(false, @vmpooler_url, 'myfakehost', nil, 12, nil) }.to raise_error(TokenError)
|
||||
expect{ Pooler.modify(false, @vmpooler_url, 'myfakehost', nil, {}) }.to raise_error(TokenError)
|
||||
end
|
||||
|
||||
it "modifies the TTL of a vm" do
|
||||
modify_hash = { :lifetime => 12 }
|
||||
stub_request(:put, "#{@vmpooler_url}/vm/fq6qlpjlsskycq6").
|
||||
with(:body => {"{\"lifetime\":12}"=>true},
|
||||
with(:body => {'{"lifetime":12}'=>true},
|
||||
:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2', 'X-Auth-Token'=>'mytokenfile'}).
|
||||
to_return(:status => 200, :body => @modify_response_body_success, :headers => {})
|
||||
|
||||
modify_req = Pooler.modify(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', 12, nil)
|
||||
modify_req = Pooler.modify(false, @vmpooler_url, 'fq6qlpjlsskycq6', 'mytokenfile', modify_hash)
|
||||
expect(modify_req["ok"]).to be true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
79
spec/vmfloaty/service_spec.rb
Normal file
79
spec/vmfloaty/service_spec.rb
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
require_relative '../../lib/vmfloaty/service'
|
||||
|
||||
describe Service do
|
||||
|
||||
describe '#initialize' do
|
||||
it 'store configuration options' do
|
||||
options = MockOptions.new({})
|
||||
config = {'url' => 'http://example.url'}
|
||||
service = Service.new(options, config)
|
||||
expect(service.config).to include config
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_new_token' do
|
||||
it 'prompts the user for their password and retrieves a token' do
|
||||
config = { 'user' => 'first.last', 'url' => 'http://default.url' }
|
||||
service = Service.new(MockOptions.new, config)
|
||||
allow(STDOUT).to receive(:puts).with('Enter your pooler service password:')
|
||||
allow(Commander::UI).to(receive(:password)
|
||||
.with('Enter your pooler service password:', '*')
|
||||
.and_return('hunter2'))
|
||||
allow(Auth).to(receive(:get_token)
|
||||
.with(nil, config['url'], config['user'], 'hunter2')
|
||||
.and_return('token-value'))
|
||||
expect(service.get_new_token(nil)).to eql 'token-value'
|
||||
end
|
||||
|
||||
it 'prompts the user for their username and password if the username is unknown' do
|
||||
config = { 'url' => 'http://default.url' }
|
||||
service = Service.new(MockOptions.new({}), config)
|
||||
allow(STDOUT).to receive(:puts).with 'Enter your pooler service username:'
|
||||
allow(STDOUT).to receive(:puts).with "\n"
|
||||
allow(STDIN).to receive(:gets).and_return('first.last')
|
||||
allow(Commander::UI).to(receive(:password)
|
||||
.with('Enter your pooler service password:', '*')
|
||||
.and_return('hunter2'))
|
||||
allow(Auth).to(receive(:get_token)
|
||||
.with(nil, config['url'], 'first.last', 'hunter2')
|
||||
.and_return('token-value'))
|
||||
expect(service.get_new_token(nil)).to eql 'token-value'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete_token' do
|
||||
it 'deletes a token' do
|
||||
service = Service.new(MockOptions.new,{'user' => 'first.last', 'url' => 'http://default.url'})
|
||||
allow(Commander::UI).to(receive(:password)
|
||||
.with('Enter your pooler service password:', '*')
|
||||
.and_return('hunter2'))
|
||||
allow(Auth).to(receive(:delete_token)
|
||||
.with(nil, 'http://default.url', 'first.last', 'hunter2', 'token-value')
|
||||
.and_return('ok' => true))
|
||||
expect(service.delete_token(nil, 'token-value')).to eql({'ok' => true})
|
||||
end
|
||||
end
|
||||
|
||||
describe '#token_status' do
|
||||
it 'reports the status of a token' do
|
||||
config = {
|
||||
'user' => 'first.last',
|
||||
'url' => 'http://default.url'
|
||||
}
|
||||
options = MockOptions.new('token' => 'token-value')
|
||||
service = Service.new(options, config)
|
||||
status = {
|
||||
'ok' => true,
|
||||
'user' => config['user'],
|
||||
'created' => '2017-09-22 02:04:18 +0000',
|
||||
'last_accessed' => '2017-09-22 02:04:28 +0000',
|
||||
'reserved_hosts' => []
|
||||
}
|
||||
allow(Auth).to(receive(:token_status)
|
||||
.with(nil, config['url'], 'token-value')
|
||||
.and_return(status))
|
||||
expect(service.token_status(nil, 'token-value')).to eql(status)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,18 +1,115 @@
|
|||
require 'spec_helper'
|
||||
require 'json'
|
||||
require 'commander/command'
|
||||
require_relative '../../lib/vmfloaty/utils'
|
||||
|
||||
describe Utils do
|
||||
|
||||
describe "#get_hosts" do
|
||||
describe "#format_hosts" do
|
||||
before :each do
|
||||
@hostname_hash = "{\"ok\":true,\"debian-7-i386\":{\"hostname\":[\"sc0o4xqtodlul5w\",\"4m4dkhqiufnjmxy\"]},\"debian-7-x86_64\":{\"hostname\":\"zb91y9qbrbf6d3q\"},\"domain\":\"company.com\"}"
|
||||
@format_hash = "{\"debian-7-i386\":[\"sc0o4xqtodlul5w.company.com\",\"4m4dkhqiufnjmxy.company.com\"],\"debian-7-x86_64\":\"zb91y9qbrbf6d3q.company.com\"}"
|
||||
@vmpooler_response_body ='{
|
||||
"ok": true,
|
||||
"domain": "delivery.mycompany.net",
|
||||
"ubuntu-1610-x86_64": {
|
||||
"hostname": ["gdoy8q3nckuob0i", "ctnktsd0u11p9tm"]
|
||||
},
|
||||
"centos-7-x86_64": {
|
||||
"hostname": "dlgietfmgeegry2"
|
||||
}
|
||||
}'
|
||||
@nonstandard_response_body = '{
|
||||
"ok": true,
|
||||
"solaris-10-sparc": {
|
||||
"hostname": ["sol10-10.delivery.mycompany.net", "sol10-11.delivery.mycompany.net"]
|
||||
},
|
||||
"ubuntu-16.04-power8": {
|
||||
"hostname": "power8-ubuntu16.04-6.delivery.mycompany.net"
|
||||
}
|
||||
}'
|
||||
@vmpooler_output = <<-OUT
|
||||
- gdoy8q3nckuob0i.delivery.mycompany.net (ubuntu-1610-x86_64)
|
||||
- ctnktsd0u11p9tm.delivery.mycompany.net (ubuntu-1610-x86_64)
|
||||
- dlgietfmgeegry2.delivery.mycompany.net (centos-7-x86_64)
|
||||
OUT
|
||||
@nonstandard_output = <<-OUT
|
||||
- sol10-10.delivery.mycompany.net (solaris-10-sparc)
|
||||
- sol10-11.delivery.mycompany.net (solaris-10-sparc)
|
||||
- power8-ubuntu16.04-6.delivery.mycompany.net (ubuntu-16.04-power8)
|
||||
OUT
|
||||
end
|
||||
|
||||
it "formats a hostname hash into os, hostnames, and domain name" do
|
||||
it "formats a hostname hash from vmpooler into a list that includes the os" do
|
||||
expect { Utils.format_hosts(JSON.parse(@vmpooler_response_body)) }.to output( @vmpooler_output).to_stdout_from_any_process
|
||||
end
|
||||
|
||||
expect(Utils.format_hosts(JSON.parse(@hostname_hash))).to eq @format_hash
|
||||
it "formats a hostname hash from the nonstandard pooler into a list that includes the os" do
|
||||
expect { Utils.format_hosts(JSON.parse(@nonstandard_response_body)) }.to output(@nonstandard_output).to_stdout_from_any_process
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_service_object" do
|
||||
it "assumes vmpooler by default" do
|
||||
expect(Utils.get_service_object).to be Pooler
|
||||
end
|
||||
|
||||
it "uses nspooler when told explicitly" do
|
||||
expect(Utils.get_service_object "nspooler").to be NonstandardPooler
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_service_config" do
|
||||
before :each do
|
||||
@default_config = {
|
||||
"url" => "http://default.url",
|
||||
"user" => "first.last.default",
|
||||
"token" => "default-token",
|
||||
}
|
||||
@services_config = {
|
||||
"services" => {
|
||||
"vm" => {
|
||||
"url" => "http://vmpooler.url",
|
||||
"user" => "first.last.vmpooler",
|
||||
"token" => "vmpooler-token"
|
||||
},
|
||||
"ns" => {
|
||||
"url" => "http://nspooler.url",
|
||||
"user" => "first.last.nspooler",
|
||||
"token" => "nspooler-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it "returns the first service configured under 'services' as the default if available" do
|
||||
config = @default_config.merge @services_config
|
||||
options = MockOptions.new({})
|
||||
expect(Utils.get_service_config(config, options)).to include @services_config['services']['vm']
|
||||
end
|
||||
|
||||
it "allows selection by configured service key" do
|
||||
config = @default_config.merge @services_config
|
||||
options = MockOptions.new({:service => "ns"})
|
||||
expect(Utils.get_service_config(config, options)).to include @services_config['services']['ns']
|
||||
end
|
||||
|
||||
it "uses top-level service config values as defaults when configured service values are missing" do
|
||||
config = @default_config.merge @services_config
|
||||
config["services"]['vm'].delete 'url'
|
||||
options = MockOptions.new({:service => "vm"})
|
||||
expect(Utils.get_service_config(config, options)['url']).to eq 'http://default.url'
|
||||
end
|
||||
|
||||
it "raises an error if passed a service name that hasn't been configured" do
|
||||
config = @default_config.merge @services_config
|
||||
options = MockOptions.new({:service => "none"})
|
||||
expect { Utils.get_service_config(config, options) }.to raise_error ArgumentError
|
||||
end
|
||||
|
||||
it "prioritizes values passed as command line options over configuration options" do
|
||||
config = @default_config
|
||||
options = MockOptions.new({:url => "http://alternate.url", :token => "alternate-token"})
|
||||
expected = config.merge({"url" => "http://alternate.url", "token" => "alternate-token"})
|
||||
expect(Utils.get_service_config(config, options)).to include expected
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -32,60 +129,97 @@ describe Utils do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#prettyprint_hosts' do
|
||||
let(:host_without_tags) { 'mcpy42eqjxli9g2' }
|
||||
let(:host_with_tags) { 'aiydvzpg23r415q' }
|
||||
describe '#pretty_print_hosts' do
|
||||
let(:url) { 'http://pooler.example.com' }
|
||||
|
||||
let(:host_info_with_tags) do
|
||||
{
|
||||
host_with_tags => {
|
||||
"template" => "redhat-7-x86_64",
|
||||
"lifetime" => 48,
|
||||
"running" => 7.67,
|
||||
"tags" => {
|
||||
"user" => "bob",
|
||||
"role" => "agent"
|
||||
it 'prints a vmpooler output with host fqdn, template and duration info' do
|
||||
hostname = 'mcpy42eqjxli9g2'
|
||||
response_body = { hostname => {
|
||||
'template' => 'ubuntu-1604-x86_64',
|
||||
'lifetime' => 12,
|
||||
'running' => 9.66,
|
||||
'state' => 'running',
|
||||
'ip' => '127.0.0.1',
|
||||
'domain' => 'delivery.mycompany.net'
|
||||
}}
|
||||
output = "- mcpy42eqjxli9g2.delivery.mycompany.net (ubuntu-1604-x86_64, 9.66/12 hours)"
|
||||
|
||||
expect(Utils).to receive(:puts).with(output)
|
||||
|
||||
service = Service.new(MockOptions.new, {'url' => url})
|
||||
allow(service).to receive(:query)
|
||||
.with(nil, hostname)
|
||||
.and_return(response_body)
|
||||
|
||||
Utils.pretty_print_hosts(nil, service, hostname)
|
||||
end
|
||||
|
||||
it 'prints a vmpooler output with host fqdn, template, duration info, and tags when supplied' do
|
||||
hostname = 'aiydvzpg23r415q'
|
||||
response_body = { hostname => {
|
||||
'template' => 'redhat-7-x86_64',
|
||||
'lifetime' => 48,
|
||||
'running' => 7.67,
|
||||
'state' => 'running',
|
||||
'tags' => {
|
||||
'user' => 'bob',
|
||||
'role' => 'agent'
|
||||
},
|
||||
"domain" => "delivery.puppetlabs.net"
|
||||
}
|
||||
}
|
||||
'ip' => '127.0.0.1',
|
||||
'domain' => 'delivery.mycompany.net'
|
||||
}}
|
||||
output = "- aiydvzpg23r415q.delivery.mycompany.net (redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
|
||||
|
||||
expect(Utils).to receive(:puts).with(output)
|
||||
|
||||
service = Service.new(MockOptions.new, {'url' => url})
|
||||
allow(service).to receive(:query)
|
||||
.with(nil, hostname)
|
||||
.and_return(response_body)
|
||||
|
||||
Utils.pretty_print_hosts(nil, service, hostname)
|
||||
end
|
||||
|
||||
let(:host_info_without_tags) do
|
||||
{
|
||||
host_without_tags => {
|
||||
"template" => "ubuntu-1604-x86_64",
|
||||
"lifetime" => 12,
|
||||
"running" => 9.66,
|
||||
"domain" => "delivery.puppetlabs.net"
|
||||
}
|
||||
}
|
||||
it 'prints a nonstandard pooler output with host, template, and time remaining' do
|
||||
hostname = "sol11-9.delivery.mycompany.net"
|
||||
response_body = { hostname => {
|
||||
'fqdn' => hostname,
|
||||
'os_triple' => 'solaris-11-sparc',
|
||||
'reserved_by_user' => 'first.last',
|
||||
'reserved_for_reason' => '',
|
||||
'hours_left_on_reservation' => 35.89
|
||||
}}
|
||||
output = "- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining)"
|
||||
|
||||
expect(Utils).to receive(:puts).with(output)
|
||||
|
||||
service = Service.new(MockOptions.new, {'url' => url, 'type' => 'ns'})
|
||||
allow(service).to receive(:query)
|
||||
.with(nil, hostname)
|
||||
.and_return(response_body)
|
||||
|
||||
Utils.pretty_print_hosts(nil, service, hostname)
|
||||
end
|
||||
|
||||
let(:output_with_tags) { "- #{host_with_tags}.delivery.puppetlabs.net (redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)" }
|
||||
let(:output_without_tags) { "- #{host_without_tags}.delivery.puppetlabs.net (ubuntu-1604-x86_64, 9.66/12 hours)" }
|
||||
it 'prints a nonstandard pooler output with host, template, time remaining, and reason' do
|
||||
hostname = 'sol11-9.delivery.mycompany.net'
|
||||
response_body = { hostname => {
|
||||
'fqdn' => hostname,
|
||||
'os_triple' => 'solaris-11-sparc',
|
||||
'reserved_by_user' => 'first.last',
|
||||
'reserved_for_reason' => 'testing',
|
||||
'hours_left_on_reservation' => 35.89
|
||||
}}
|
||||
output = "- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)"
|
||||
|
||||
it 'prints an output with host fqdn, template and duration info' do
|
||||
allow(Utils).to receive(:get_vm_info).
|
||||
with(host_without_tags, false, url).
|
||||
and_return(host_info_without_tags)
|
||||
expect(Utils).to receive(:puts).with(output)
|
||||
|
||||
expect(Utils).to receive(:puts).with("Running VMs:")
|
||||
expect(Utils).to receive(:puts).with(output_without_tags)
|
||||
service = Service.new(MockOptions.new, {'url' => url, 'type' => 'ns'})
|
||||
allow(service).to receive(:query)
|
||||
.with(nil, hostname)
|
||||
.and_return(response_body)
|
||||
|
||||
Utils.prettyprint_hosts(host_without_tags, false, url)
|
||||
end
|
||||
|
||||
it 'prints an output with host fqdn, template, duration info, and tags when supplied' do
|
||||
allow(Utils).to receive(:get_vm_info).
|
||||
with(host_with_tags, false, url).
|
||||
and_return(host_info_with_tags)
|
||||
|
||||
expect(Utils).to receive(:puts).with("Running VMs:")
|
||||
expect(Utils).to receive(:puts).with(output_with_tags)
|
||||
|
||||
Utils.prettyprint_hosts(host_with_tags, false, url)
|
||||
Utils.pretty_print_hosts(nil, service, hostname)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue