vmfloaty 0.7.9 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -1
- data/lib/vmfloaty/errors.rb +6 -0
- data/lib/vmfloaty/nonstandard_pooler.rb +135 -0
- data/lib/vmfloaty/pooler.rb +20 -8
- data/lib/vmfloaty/service.rb +133 -0
- data/lib/vmfloaty/utils.rb +163 -73
- data/lib/vmfloaty/version.rb +1 -1
- data/lib/vmfloaty.rb +191 -327
- data/spec/spec_helper.rb +7 -0
- data/spec/vmfloaty/nonstandard_pooler_spec.rb +325 -0
- data/spec/vmfloaty/pooler_spec.rb +4 -3
- data/spec/vmfloaty/service_spec.rb +79 -0
- data/spec/vmfloaty/utils_spec.rb +183 -49
- metadata +10 -4
data/lib/vmfloaty/utils.rb
CHANGED
@@ -1,27 +1,62 @@
|
|
1
|
-
|
2
1
|
require 'vmfloaty/pooler'
|
2
|
+
require 'vmfloaty/nonstandard_pooler'
|
3
3
|
|
4
4
|
class Utils
|
5
5
|
# TODO: Takes the json response body from an HTTP GET
|
6
6
|
# request and "pretty prints" it
|
7
|
-
def self.format_hosts(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
domain
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
def self.format_hosts(response_body)
|
8
|
+
# vmpooler response body example when `floaty get` arguments are `ubuntu-1610-x86_64=2 centos-7-x86_64`:
|
9
|
+
# {
|
10
|
+
# "ok": true,
|
11
|
+
# "domain": "delivery.mycompany.net",
|
12
|
+
# "ubuntu-1610-x86_64": {
|
13
|
+
# "hostname": ["gdoy8q3nckuob0i", "ctnktsd0u11p9tm"]
|
14
|
+
# },
|
15
|
+
# "centos-7-x86_64": {
|
16
|
+
# "hostname": "dlgietfmgeegry2"
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
|
20
|
+
# nonstandard pooler response body example when `floaty get` arguments are `solaris-11-sparc=2 ubuntu-16.04-power8`:
|
21
|
+
# {
|
22
|
+
# "ok": true,
|
23
|
+
# "solaris-10-sparc": {
|
24
|
+
# "hostname": ["sol10-10.delivery.mycompany.net", "sol10-11.delivery.mycompany.net"]
|
25
|
+
# },
|
26
|
+
# "ubuntu-16.04-power8": {
|
27
|
+
# "hostname": "power8-ubuntu1604-6.delivery.mycompany.net"
|
28
|
+
# }
|
29
|
+
# }
|
30
|
+
|
31
|
+
unless response_body.delete('ok')
|
32
|
+
raise ArgumentError, "Bad GET response passed to format_hosts: #{response_body.to_json}"
|
33
|
+
end
|
34
|
+
|
35
|
+
hostnames = []
|
36
|
+
|
37
|
+
# vmpooler reports the domain separately from the hostname
|
38
|
+
domain = response_body.delete('domain')
|
39
|
+
|
40
|
+
if domain
|
41
|
+
# vmpooler output
|
42
|
+
response_body.each do |os, hosts|
|
43
|
+
if hosts['hostname'].kind_of?(Array)
|
44
|
+
hosts['hostname'].map!{ |host| hostnames << host + "." + domain + " (#{os})"}
|
16
45
|
else
|
17
|
-
|
46
|
+
hostnames << hosts["hostname"] + ".#{domain} (#{os})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
response_body.each do |os, hosts|
|
51
|
+
if hosts['hostname'].kind_of?(Array)
|
52
|
+
hosts['hostname'].map!{ |host| hostnames << host + " (#{os})" }
|
53
|
+
else
|
54
|
+
hostnames << hosts['hostname'] + " (#{os})"
|
18
55
|
end
|
19
|
-
|
20
|
-
host_hash[type] = hosts["hostname"]
|
21
56
|
end
|
22
57
|
end
|
23
58
|
|
24
|
-
|
59
|
+
hostnames.map { |hostname| puts "- #{hostname}" }
|
25
60
|
end
|
26
61
|
|
27
62
|
def self.generate_os_hash(os_args)
|
@@ -46,79 +81,134 @@ class Utils
|
|
46
81
|
os_types
|
47
82
|
end
|
48
83
|
|
49
|
-
def self.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
84
|
+
def self.pretty_print_hosts(verbose, service, hostnames = [])
|
85
|
+
hostnames = [hostnames] unless hostnames.is_a? Array
|
86
|
+
hostnames.each do |hostname|
|
87
|
+
begin
|
88
|
+
response = service.query(verbose, hostname)
|
89
|
+
host_data = response[hostname]
|
90
|
+
|
91
|
+
case service.type
|
92
|
+
when 'Pooler'
|
93
|
+
tag_pairs = []
|
94
|
+
unless host_data['tags'].nil?
|
95
|
+
tag_pairs = host_data['tags'].map {|key, value| "#{key}: #{value}"}
|
96
|
+
end
|
97
|
+
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
98
|
+
metadata = [host_data['template'], duration, *tag_pairs]
|
99
|
+
puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(", ")})"
|
100
|
+
when 'NonstandardPooler'
|
101
|
+
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
102
|
+
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
103
|
+
unless host_data['reserved_for_reason'].empty?
|
104
|
+
line += ", reason: #{host_data['reserved_for_reason']}"
|
105
|
+
end
|
106
|
+
line += ')'
|
107
|
+
puts line
|
108
|
+
else
|
109
|
+
raise "Invalid service type #{service.type}"
|
110
|
+
end
|
111
|
+
rescue => e
|
112
|
+
STDERR.puts("Something went wrong while trying to gather information on #{hostname}:")
|
113
|
+
STDERR.puts(e)
|
60
114
|
end
|
61
115
|
end
|
62
|
-
vms
|
63
116
|
end
|
64
117
|
|
65
|
-
def self.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
118
|
+
def self.pretty_print_status(verbose, service)
|
119
|
+
status_response = service.status(verbose)
|
120
|
+
|
121
|
+
case service.type
|
122
|
+
when 'Pooler'
|
123
|
+
message = status_response['status']['message']
|
124
|
+
pools = status_response['pools']
|
125
|
+
pools.select! {|_, pool| pool['ready'] < pool['max']} unless verbose
|
126
|
+
|
127
|
+
width = pools.keys.map(&:length).max
|
128
|
+
pools.each do |name, pool|
|
129
|
+
begin
|
130
|
+
max = pool['max']
|
131
|
+
ready = pool['ready']
|
132
|
+
pending = pool['pending']
|
133
|
+
missing = max - ready - pending
|
134
|
+
char = 'o'
|
135
|
+
puts "#{name.ljust(width)} #{(char*ready).green}#{(char*pending).yellow}#{(char*missing).red}"
|
136
|
+
rescue => e
|
137
|
+
puts "#{name.ljust(width)} #{e.red}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
puts message.colorize(status_response['status']['ok'] ? :default : :red)
|
141
|
+
when 'NonstandardPooler'
|
142
|
+
pools = status_response
|
143
|
+
pools.delete 'ok'
|
144
|
+
pools.select! {|_, pool| pool['available_hosts'] < pool['total_hosts']} unless verbose
|
145
|
+
|
146
|
+
width = pools.keys.map(&:length).max
|
147
|
+
pools.each do |name, pool|
|
148
|
+
begin
|
149
|
+
max = pool['total_hosts']
|
150
|
+
ready = pool['available_hosts']
|
151
|
+
pending = pool['pending'] || 0 # not available for nspooler
|
152
|
+
missing = max - ready - pending
|
153
|
+
char = 'o'
|
154
|
+
puts "#{name.ljust(width)} #{(char*ready).green}#{(char*pending).yellow}#{(char*missing).red}"
|
155
|
+
rescue => e
|
156
|
+
puts "#{name.ljust(width)} #{e.red}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
else
|
160
|
+
raise "Invalid service type #{service.type}"
|
80
161
|
end
|
81
162
|
end
|
82
163
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
vms = status[token]['vms']
|
88
|
-
if vms.nil?
|
89
|
-
raise "You have no running vms"
|
90
|
-
end
|
164
|
+
# Adapted from ActiveSupport
|
165
|
+
def self.strip_heredoc(str)
|
166
|
+
min_indent = str.scan(/^[ \t]*(?=\S)/).min
|
167
|
+
min_indent_size = min_indent.nil? ? 0 : min_indent.size
|
91
168
|
|
92
|
-
|
93
|
-
running_vms
|
169
|
+
str.gsub(/^[ \t]{#{min_indent_size}}/, '')
|
94
170
|
end
|
95
171
|
|
96
|
-
def self.
|
97
|
-
|
172
|
+
def self.get_service_object(type = '')
|
173
|
+
nspooler_strings = ['ns', 'nspooler', 'nonstandard', 'nonstandard_pooler']
|
174
|
+
if nspooler_strings.include? type.downcase
|
175
|
+
NonstandardPooler
|
176
|
+
else
|
177
|
+
Pooler
|
178
|
+
end
|
179
|
+
end
|
98
180
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
181
|
+
def self.get_service_config(config, options)
|
182
|
+
# The top-level url, user, and token values in the config file are treated as defaults
|
183
|
+
service_config = {
|
184
|
+
'url' => config['url'],
|
185
|
+
'user' => config['user'],
|
186
|
+
'token' => config['token'],
|
187
|
+
'type' => config['type'] || 'vmpooler'
|
188
|
+
}
|
189
|
+
|
190
|
+
if config['services']
|
191
|
+
if options.service.nil?
|
192
|
+
# If the user did not specify a service name at the command line, but configured services do exist,
|
193
|
+
# use the first configured service in the list by default.
|
194
|
+
_, values = config['services'].first
|
195
|
+
service_config.merge! values
|
196
|
+
else
|
197
|
+
# If the user provided a service name at the command line, use that service if posible, or fail
|
198
|
+
if config['services'][options.service]
|
199
|
+
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
200
|
+
service_config.merge! config['services'][options.service]
|
201
|
+
else
|
202
|
+
raise ArgumentError, "Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
|
203
|
+
end
|
110
204
|
end
|
111
205
|
end
|
112
206
|
|
113
|
-
|
114
|
-
|
115
|
-
|
207
|
+
# Prioritize an explicitly specified url, user, or token if the user provided one
|
208
|
+
service_config['url'] = options.url unless options.url.nil?
|
209
|
+
service_config['token'] = options.token unless options.token.nil?
|
210
|
+
service_config['user'] = options.user unless options.user.nil?
|
116
211
|
|
117
|
-
|
118
|
-
def self.strip_heredoc(str)
|
119
|
-
min_indent = str.scan(/^[ \t]*(?=\S)/).min
|
120
|
-
min_indent_size = min_indent.nil? ? 0 : min_indent.size
|
121
|
-
|
122
|
-
str.gsub(/^[ \t]{#{min_indent_size}}/, '')
|
212
|
+
service_config
|
123
213
|
end
|
124
214
|
end
|
data/lib/vmfloaty/version.rb
CHANGED