vmfloaty 0.11.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -75
- data/extras/completions/floaty.bash +17 -9
- data/extras/completions/floaty.zsh +43 -0
- data/lib/vmfloaty.rb +148 -23
- data/lib/vmfloaty/abs.rb +174 -69
- data/lib/vmfloaty/conf.rb +1 -1
- data/lib/vmfloaty/logger.rb +13 -0
- data/lib/vmfloaty/nonstandard_pooler.rb +1 -1
- data/lib/vmfloaty/pooler.rb +1 -1
- data/lib/vmfloaty/service.rb +22 -2
- data/lib/vmfloaty/utils.rb +126 -27
- data/lib/vmfloaty/version.rb +2 -1
- data/spec/vmfloaty/abs_spec.rb +55 -8
- data/spec/vmfloaty/utils_spec.rb +462 -73
- metadata +12 -11
data/lib/vmfloaty/utils.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'vmfloaty/abs'
|
4
4
|
require 'vmfloaty/nonstandard_pooler'
|
5
5
|
require 'vmfloaty/pooler'
|
6
|
+
require 'vmfloaty/conf'
|
6
7
|
|
7
8
|
class Utils
|
8
9
|
# TODO: Takes the json response body from an HTTP GET
|
@@ -45,6 +46,10 @@ class Utils
|
|
45
46
|
|
46
47
|
result = {}
|
47
48
|
|
49
|
+
# ABS has a job_id associated with hosts so pass that along
|
50
|
+
abs_job_id = response_body.delete('job_id')
|
51
|
+
result['job_id'] = abs_job_id unless abs_job_id.nil?
|
52
|
+
|
48
53
|
filtered_response_body = response_body.reject { |key, _| key == 'request_id' || key == 'ready' }
|
49
54
|
filtered_response_body.each do |os, value|
|
50
55
|
hostnames = Array(value['hostname'])
|
@@ -57,7 +62,8 @@ class Utils
|
|
57
62
|
|
58
63
|
def self.format_host_output(hosts)
|
59
64
|
hosts.flat_map do |os, names|
|
60
|
-
|
65
|
+
# Assume hosts are stored in Arrays and ignore everything else
|
66
|
+
names.map { |name| "- #{name} (#{os})" } if names.is_a? Array
|
61
67
|
end.join("\n")
|
62
68
|
end
|
63
69
|
|
@@ -78,41 +84,101 @@ class Utils
|
|
78
84
|
os_types
|
79
85
|
end
|
80
86
|
|
81
|
-
def self.
|
87
|
+
def self.print_fqdn_for_host(service, hostname, host_data)
|
88
|
+
case service.type
|
89
|
+
when 'ABS'
|
90
|
+
abs_hostnames = []
|
91
|
+
|
92
|
+
host_data['allocated_resources'].each do |vm_name, _i|
|
93
|
+
abs_hostnames << vm_name['hostname']
|
94
|
+
end
|
95
|
+
|
96
|
+
puts abs_hostnames.join("\n")
|
97
|
+
when 'Pooler'
|
98
|
+
puts "#{hostname}.#{host_data['domain']}"
|
99
|
+
when 'NonstandardPooler'
|
100
|
+
puts host_data['fqdn']
|
101
|
+
else
|
102
|
+
raise "Invalid service type #{service.type}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false, indent = 0)
|
107
|
+
output_target = print_to_stderr ? $stderr : $stdout
|
108
|
+
|
109
|
+
fetched_data = self.get_host_data(verbose, service, hostnames)
|
110
|
+
fetched_data.each do |hostname, host_data|
|
111
|
+
case service.type
|
112
|
+
when 'ABS'
|
113
|
+
# For ABS, 'hostname' variable is the jobID
|
114
|
+
#
|
115
|
+
# Create a vmpooler service to query each hostname there so as to get the metadata too
|
116
|
+
|
117
|
+
output_target.puts "- [JobID:#{host_data['request']['job']['id']}] <#{host_data['state']}>"
|
118
|
+
host_data['allocated_resources'].each do |allocated_resources, _i|
|
119
|
+
if (allocated_resources['engine'] == "vmpooler" || allocated_resources['engine'] == 'ondemand') && service.config["vmpooler_fallback"]
|
120
|
+
vmpooler_service = service.clone
|
121
|
+
vmpooler_service.silent = true
|
122
|
+
vmpooler_service.maybe_use_vmpooler
|
123
|
+
self.pretty_print_hosts(verbose, vmpooler_service, allocated_resources['hostname'].split('.')[0], print_to_stderr, indent+2)
|
124
|
+
else
|
125
|
+
#TODO we could add more specific metadata for the other services, nspooler and aws
|
126
|
+
output_target.puts " - #{allocated_resources['hostname']} (#{allocated_resources['type']})"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
when 'Pooler'
|
130
|
+
tag_pairs = []
|
131
|
+
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
132
|
+
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
133
|
+
metadata = [host_data['state'], host_data['template'], duration, *tag_pairs]
|
134
|
+
message = "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
|
135
|
+
if host_data['state'] && host_data['state'] == "destroyed"
|
136
|
+
output_target.puts message.colorize(:red)
|
137
|
+
else
|
138
|
+
output_target.puts message
|
139
|
+
end
|
140
|
+
when 'NonstandardPooler'
|
141
|
+
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
142
|
+
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
143
|
+
line += ", reason: #{host_data['reserved_for_reason']}" unless host_data['reserved_for_reason'].empty?
|
144
|
+
line += ')'
|
145
|
+
output_target.puts line
|
146
|
+
else
|
147
|
+
raise "Invalid service type #{service.type}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.get_host_data(verbose, service, hostnames = [])
|
153
|
+
result = {}
|
82
154
|
hostnames = [hostnames] unless hostnames.is_a? Array
|
83
155
|
hostnames.each do |hostname|
|
84
156
|
begin
|
85
157
|
response = service.query(verbose, hostname)
|
86
158
|
host_data = response[hostname]
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
159
|
+
if block_given?
|
160
|
+
yield host_data result
|
161
|
+
else
|
162
|
+
case service.type
|
163
|
+
when 'ABS'
|
164
|
+
# For ABS, 'hostname' variable is the jobID
|
165
|
+
if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
|
166
|
+
result[hostname] = host_data
|
94
167
|
end
|
168
|
+
when 'Pooler'
|
169
|
+
result[hostname] = host_data
|
170
|
+
when 'NonstandardPooler'
|
171
|
+
result[hostname] = host_data
|
172
|
+
else
|
173
|
+
raise "Invalid service type #{service.type}"
|
95
174
|
end
|
96
|
-
when 'Pooler'
|
97
|
-
tag_pairs = []
|
98
|
-
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
99
|
-
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
100
|
-
metadata = [host_data['template'], duration, *tag_pairs]
|
101
|
-
puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})"
|
102
|
-
when 'NonstandardPooler'
|
103
|
-
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
104
|
-
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
105
|
-
line += ", reason: #{host_data['reserved_for_reason']}" unless host_data['reserved_for_reason'].empty?
|
106
|
-
line += ')'
|
107
|
-
puts line
|
108
|
-
else
|
109
|
-
raise "Invalid service type #{service.type}"
|
110
175
|
end
|
111
176
|
rescue StandardError => e
|
112
177
|
FloatyLogger.error("Something went wrong while trying to gather information on #{hostname}:")
|
113
178
|
FloatyLogger.error(e)
|
114
179
|
end
|
115
180
|
end
|
181
|
+
result
|
116
182
|
end
|
117
183
|
|
118
184
|
def self.pretty_print_status(verbose, service)
|
@@ -173,12 +239,15 @@ class Utils
|
|
173
239
|
end
|
174
240
|
|
175
241
|
def self.get_service_object(type = '')
|
176
|
-
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
177
242
|
abs_strings = %w[abs alwaysbescheduling always_be_scheduling]
|
178
|
-
|
179
|
-
|
180
|
-
|
243
|
+
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
244
|
+
vmpooler_strings = %w[vmpooler]
|
245
|
+
if abs_strings.include? type.downcase
|
181
246
|
ABS
|
247
|
+
elsif nspooler_strings.include? type.downcase
|
248
|
+
NonstandardPooler
|
249
|
+
elsif vmpooler_strings.include? type.downcase
|
250
|
+
Pooler
|
182
251
|
else
|
183
252
|
Pooler
|
184
253
|
end
|
@@ -190,6 +259,7 @@ class Utils
|
|
190
259
|
'url' => config['url'],
|
191
260
|
'user' => config['user'],
|
192
261
|
'token' => config['token'],
|
262
|
+
'vmpooler_fallback' => config['vmpooler_fallback'],
|
193
263
|
'type' => config['type'] || 'vmpooler',
|
194
264
|
}
|
195
265
|
|
@@ -206,6 +276,9 @@ class Utils
|
|
206
276
|
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
207
277
|
service_config.merge! config['services'][options.service]
|
208
278
|
end
|
279
|
+
# No config file but service is declared on command line
|
280
|
+
elsif !config['services'] && options.service
|
281
|
+
service_config['type'] = options.service
|
209
282
|
end
|
210
283
|
|
211
284
|
# Prioritize an explicitly specified url, user, or token if the user provided one
|
@@ -216,4 +289,30 @@ class Utils
|
|
216
289
|
|
217
290
|
service_config
|
218
291
|
end
|
292
|
+
|
293
|
+
# This method gets the vmpooler service configured in ~/.vmfloaty
|
294
|
+
def self.get_vmpooler_service_config(vmpooler_fallback)
|
295
|
+
config = Conf.read_config
|
296
|
+
# The top-level url, user, and token values in the config file are treated as defaults
|
297
|
+
service_config = {
|
298
|
+
'url' => config['url'],
|
299
|
+
'user' => config['user'],
|
300
|
+
'token' => config['token'],
|
301
|
+
'type' => 'vmpooler',
|
302
|
+
}
|
303
|
+
|
304
|
+
# at a minimum, the url needs to be configured
|
305
|
+
if config['services'] && config['services'][vmpooler_fallback] && config['services'][vmpooler_fallback]['url']
|
306
|
+
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
307
|
+
service_config.merge! config['services'][vmpooler_fallback]
|
308
|
+
else
|
309
|
+
if vmpooler_fallback.nil?
|
310
|
+
raise ArgumentError, "The abs service should have a key named 'vmpooler_fallback' in ~/.vmfloaty.yml with a value that points to a vmpooler service name use this format:\nservices:\n myabs:\n url: 'http://abs.com'\n user: 'superman'\n token: 'kryptonite'\n vmpooler_fallback: 'myvmpooler'\n myvmpooler:\n url: 'http://vmpooler.com'\n user: 'superman'\n token: 'kryptonite'"
|
311
|
+
else
|
312
|
+
raise ArgumentError, "Could not find a configured service named '#{vmpooler_fallback}' in ~/.vmfloaty.yml use this format:\nservices:\n #{vmpooler_fallback}:\n url: 'http://vmpooler.com'\n user: 'superman'\n token: 'kryptonite'"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
service_config
|
317
|
+
end
|
219
318
|
end
|
data/lib/vmfloaty/version.rb
CHANGED
data/spec/vmfloaty/abs_spec.rb
CHANGED
@@ -9,15 +9,60 @@ describe ABS do
|
|
9
9
|
before :each do
|
10
10
|
end
|
11
11
|
|
12
|
+
describe '#list' do
|
13
|
+
it 'skips empty platforms and lists aws' do
|
14
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/vmpooler").
|
15
|
+
to_return(:status => 200, :body => "", :headers => {})
|
16
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/ondemand_vmpooler").
|
17
|
+
to_return(:status => 200, :body => "", :headers => {})
|
18
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/nspooler").
|
19
|
+
to_return(:status => 200, :body => "", :headers => {})
|
20
|
+
body = '{
|
21
|
+
"aws_platforms": [
|
22
|
+
"amazon-6-x86_64",
|
23
|
+
"amazon-7-x86_64",
|
24
|
+
"amazon-7-arm64",
|
25
|
+
"centos-7-x86-64-west",
|
26
|
+
"redhat-8-arm64"
|
27
|
+
]
|
28
|
+
}'
|
29
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/aws").
|
30
|
+
to_return(:status => 200, :body => body, :headers => {})
|
31
|
+
|
32
|
+
|
33
|
+
results = ABS.list(false, "http://foo")
|
34
|
+
|
35
|
+
expect(results).to include("amazon-6-x86_64", "amazon-7-x86_64", "amazon-7-arm64", "centos-7-x86-64-west", "redhat-8-arm64")
|
36
|
+
end
|
37
|
+
it 'legacy JSON string, prior to PR 306' do
|
38
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/vmpooler").
|
39
|
+
to_return(:status => 200, :body => "", :headers => {})
|
40
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/ondemand_vmpooler").
|
41
|
+
to_return(:status => 200, :body => "", :headers => {})
|
42
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/nspooler").
|
43
|
+
to_return(:status => 200, :body => "", :headers => {})
|
44
|
+
body = '{
|
45
|
+
"aws_platforms": "[\"amazon-6-x86_64\",\"amazon-7-x86_64\",\"amazon-7-arm64\",\"centos-7-x86-64-west\",\"redhat-8-arm64\"]"
|
46
|
+
}'
|
47
|
+
stub_request(:get, "http://foo/api/v2/status/platforms/aws").
|
48
|
+
to_return(:status => 200, :body => body, :headers => {})
|
49
|
+
|
50
|
+
results = ABS.list(false, "http://foo")
|
51
|
+
|
52
|
+
expect(results).to include("amazon-6-x86_64", "amazon-7-x86_64", "amazon-7-arm64", "centos-7-x86-64-west", "redhat-8-arm64")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
12
56
|
describe '#format' do
|
13
|
-
it 'returns an hash formatted like a vmpooler return' do
|
57
|
+
it 'returns an hash formatted like a vmpooler return, plus the job_id' do
|
58
|
+
job_id = "generated_by_floaty_12345"
|
14
59
|
abs_formatted_response = [
|
15
60
|
{ 'hostname' => 'aaaaaaaaaaaaaaa.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64', 'engine' => 'vmpooler' },
|
16
61
|
{ 'hostname' => 'aaaaaaaaaaaaaab.delivery.puppetlabs.net', 'type' => 'centos-7.2-x86_64', 'engine' => 'vmpooler' },
|
17
62
|
{ 'hostname' => 'aaaaaaaaaaaaaac.delivery.puppetlabs.net', 'type' => 'ubuntu-7.2-x86_64', 'engine' => 'vmpooler' },
|
18
63
|
]
|
19
64
|
|
20
|
-
vmpooler_formatted_response = ABS.translated(abs_formatted_response)
|
65
|
+
vmpooler_formatted_response = ABS.translated(abs_formatted_response, job_id)
|
21
66
|
|
22
67
|
vmpooler_formatted_compare = {
|
23
68
|
'centos-7.2-x86_64' => {},
|
@@ -29,6 +74,8 @@ describe ABS do
|
|
29
74
|
|
30
75
|
vmpooler_formatted_compare['ok'] = true
|
31
76
|
|
77
|
+
vmpooler_formatted_compare['job_id'] = job_id
|
78
|
+
|
32
79
|
expect(vmpooler_formatted_response).to eq(vmpooler_formatted_compare)
|
33
80
|
vmpooler_formatted_response.delete('ok')
|
34
81
|
vmpooler_formatted_compare.delete('ok')
|
@@ -68,9 +115,9 @@ describe ABS do
|
|
68
115
|
# rubocop:disable Layout/LineLength
|
69
116
|
@active_requests_response = '
|
70
117
|
[
|
71
|
-
|
118
|
+
{ "state":"allocated","last_processed":"2019-12-16 23:00:34 +0000","allocated_resources":[{"hostname":"take-this.delivery.puppetlabs.net","type":"win-2012r2-x86_64","engine":"vmpooler"}],"audit_log":{"2019-12-13 16:45:29 +0000":"Allocated take-this.delivery.puppetlabs.net for job 1576255517241"},"request":{"resources":{"win-2012r2-x86_64":1},"job":{"id":"1576255517241","tags":{"user":"test-user"},"user":"test-user","time-received":1576255519},"priority":1}},
|
72
119
|
"null",
|
73
|
-
|
120
|
+
{"state":"allocated","last_processed":"2019-12-16 23:00:34 +0000","allocated_resources":[{"hostname":"not-this.delivery.puppetlabs.net","type":"win-2012r2-x86_64","engine":"vmpooler"}],"audit_log":{"2019-12-13 16:46:14 +0000":"Allocated not-this.delivery.puppetlabs.net for job 1576255565159"},"request":{"resources":{"win-2012r2-x86_64":1},"job":{"id":"1576255565159","tags":{"user":"not-test-user"},"user":"not-test-user","time-received":1576255566},"priority":1}}
|
74
121
|
]'
|
75
122
|
# rubocop:enable Layout/LineLength
|
76
123
|
@token = 'utpg2i2xswor6h8ttjhu3d47z53yy47y'
|
@@ -78,7 +125,7 @@ describe ABS do
|
|
78
125
|
end
|
79
126
|
|
80
127
|
it 'will skip a line with a null value returned from abs' do
|
81
|
-
stub_request(:get, 'https://abs.example.com/status/queue')
|
128
|
+
stub_request(:get, 'https://abs.example.com/api/v2/status/queue')
|
82
129
|
.to_return(:status => 200, :body => @active_requests_response, :headers => {})
|
83
130
|
|
84
131
|
ret = ABS.get_active_requests(false, @abs_url, @test_user)
|
@@ -98,7 +145,7 @@ describe ABS do
|
|
98
145
|
# rubocop:disable Layout/LineLength
|
99
146
|
@active_requests_response = '
|
100
147
|
[
|
101
|
-
|
148
|
+
{ "state":"allocated", "last_processed":"2020-01-17 22:29:13 +0000", "allocated_resources":[{"hostname":"craggy-chord.delivery.puppetlabs.net", "type":"centos-7-x86_64", "engine":"vmpooler"}, {"hostname":"visible-revival.delivery.puppetlabs.net", "type":"centos-7-x86_64", "engine":"vmpooler"}], "audit_log":{"2020-01-17 22:28:45 +0000":"Allocated craggy-chord.delivery.puppetlabs.net, visible-revival.delivery.puppetlabs.net for job 1579300120799"}, "request":{"resources":{"centos-7-x86_64":2}, "job":{"id":"1579300120799", "tags":{"user":"test-user"}, "user":"test-user", "time-received":1579300120}, "priority":3}}
|
102
149
|
]'
|
103
150
|
@return_request = { '{"job_id":"1579300120799","hosts":{"hostname":"craggy-chord.delivery.puppetlabs.net","type":"centos-7-x86_64","engine":"vmpooler"},{"hostname":"visible-revival.delivery.puppetlabs.net","type":"centos-7-x86_64","engine":"vmpooler"}}'=>true }
|
104
151
|
# rubocop:enable Layout/LineLength
|
@@ -109,9 +156,9 @@ describe ABS do
|
|
109
156
|
end
|
110
157
|
|
111
158
|
it 'will delete the whole job' do
|
112
|
-
stub_request(:get, 'https://abs.example.com/status/queue')
|
159
|
+
stub_request(:get, 'https://abs.example.com/api/v2/status/queue')
|
113
160
|
.to_return(:status => 200, :body => @active_requests_response, :headers => {})
|
114
|
-
stub_request(:post, 'https://abs.example.com/return')
|
161
|
+
stub_request(:post, 'https://abs.example.com/api/v2/return')
|
115
162
|
.with(:body => @return_request)
|
116
163
|
.to_return(:status => 200, :body => 'OK', :headers => {})
|
117
164
|
|
data/spec/vmfloaty/utils_spec.rb
CHANGED
@@ -5,6 +5,11 @@ require 'json'
|
|
5
5
|
require 'commander/command'
|
6
6
|
require_relative '../../lib/vmfloaty/utils'
|
7
7
|
|
8
|
+
# allow changing config in service for tests
|
9
|
+
class Service
|
10
|
+
attr_writer :config
|
11
|
+
end
|
12
|
+
|
8
13
|
describe Utils do
|
9
14
|
describe '#standardize_hostnames' do
|
10
15
|
before :each do
|
@@ -77,9 +82,17 @@ describe Utils do
|
|
77
82
|
expect(Utils.get_service_object).to be Pooler
|
78
83
|
end
|
79
84
|
|
85
|
+
it 'uses abs when told explicitly' do
|
86
|
+
expect(Utils.get_service_object('abs')).to be ABS
|
87
|
+
end
|
88
|
+
|
80
89
|
it 'uses nspooler when told explicitly' do
|
81
90
|
expect(Utils.get_service_object('nspooler')).to be NonstandardPooler
|
82
91
|
end
|
92
|
+
|
93
|
+
it 'uses vmpooler when told explicitly' do
|
94
|
+
expect(Utils.get_service_object('vmpooler')).to be Pooler
|
95
|
+
end
|
83
96
|
end
|
84
97
|
|
85
98
|
describe '#get_service_config' do
|
@@ -154,97 +167,473 @@ describe Utils do
|
|
154
167
|
end
|
155
168
|
end
|
156
169
|
|
157
|
-
describe '#
|
170
|
+
describe '#print_fqdn_for_host' do
|
158
171
|
let(:url) { 'http://pooler.example.com' }
|
159
172
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
173
|
+
subject { Utils.print_fqdn_for_host(service, hostname, host_data) }
|
174
|
+
|
175
|
+
describe 'with vmpooler host' do
|
176
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url) }
|
177
|
+
let(:hostname) { 'mcpy42eqjxli9g2' }
|
178
|
+
let(:domain) { 'delivery.mycompany.net' }
|
179
|
+
let(:fqdn) { [hostname, domain].join('.') }
|
180
|
+
|
181
|
+
let(:host_data) do
|
182
|
+
{
|
183
|
+
'template' => 'ubuntu-1604-x86_64',
|
184
|
+
'lifetime' => 12,
|
185
|
+
'running' => 9.66,
|
186
|
+
'state' => 'running',
|
187
|
+
'ip' => '127.0.0.1',
|
188
|
+
'domain' => domain,
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'outputs fqdn for host' do
|
193
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
194
|
+
|
195
|
+
subject
|
196
|
+
end
|
180
197
|
end
|
181
198
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
'
|
191
|
-
'
|
192
|
-
}
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
199
|
+
describe 'with nonstandard pooler host' do
|
200
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
|
201
|
+
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
|
202
|
+
let(:host_data) do
|
203
|
+
{
|
204
|
+
'fqdn' => hostname,
|
205
|
+
'os_triple' => 'solaris-11-sparc',
|
206
|
+
'reserved_by_user' => 'first.last',
|
207
|
+
'reserved_for_reason' => '',
|
208
|
+
'hours_left_on_reservation' => 35.89,
|
209
|
+
}
|
210
|
+
end
|
211
|
+
let(:fqdn) { hostname } # for nspooler these are the same
|
212
|
+
|
213
|
+
it 'outputs fqdn for host' do
|
214
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
215
|
+
|
216
|
+
subject
|
217
|
+
end
|
218
|
+
end
|
197
219
|
|
198
|
-
|
220
|
+
describe 'with ABS host' do
|
221
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
222
|
+
let(:hostname) { '1597952189390' }
|
223
|
+
let(:fqdn) { 'example-noun.delivery.puppetlabs.net' }
|
224
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
225
|
+
|
226
|
+
# This seems to be the miminal stub response from ABS for the current output
|
227
|
+
let(:host_data) do
|
228
|
+
{
|
229
|
+
'state' => 'allocated',
|
230
|
+
'allocated_resources' => [
|
231
|
+
{
|
232
|
+
'hostname' => fqdn,
|
233
|
+
'type' => template,
|
234
|
+
'enging' => 'vmpooler',
|
235
|
+
},
|
236
|
+
],
|
237
|
+
'request' => {
|
238
|
+
'job' => {
|
239
|
+
'id' => hostname,
|
240
|
+
}
|
241
|
+
},
|
242
|
+
}
|
243
|
+
end
|
199
244
|
|
200
|
-
|
201
|
-
|
202
|
-
.with(nil, hostname)
|
203
|
-
.and_return(response_body)
|
245
|
+
it 'outputs fqdn for host' do
|
246
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
204
247
|
|
205
|
-
|
248
|
+
subject
|
249
|
+
end
|
206
250
|
end
|
251
|
+
end
|
207
252
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
'os_triple' => 'solaris-11-sparc',
|
213
|
-
'reserved_by_user' => 'first.last',
|
214
|
-
'reserved_for_reason' => '',
|
215
|
-
'hours_left_on_reservation' => 35.89,
|
216
|
-
} }
|
217
|
-
output = '- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining)'
|
218
|
-
|
219
|
-
expect(STDOUT).to receive(:puts).with(output)
|
253
|
+
describe '#pretty_print_hosts' do
|
254
|
+
let(:url) { 'http://pooler.example.com' }
|
255
|
+
let(:verbose) { nil }
|
256
|
+
let(:print_to_stderr) { false }
|
220
257
|
|
221
|
-
|
258
|
+
before(:each) do
|
222
259
|
allow(service).to receive(:query)
|
223
|
-
.with(
|
260
|
+
.with(anything, hostname)
|
224
261
|
.and_return(response_body)
|
262
|
+
end
|
225
263
|
|
226
|
-
|
264
|
+
subject { Utils.pretty_print_hosts(verbose, service, hostname, print_to_stderr) }
|
265
|
+
|
266
|
+
describe 'with vmpooler service' do
|
267
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url) }
|
268
|
+
|
269
|
+
let(:hostname) { 'mcpy42eqjxli9g2' }
|
270
|
+
let(:domain) { 'delivery.mycompany.net' }
|
271
|
+
let(:fqdn) { [hostname, domain].join('.') }
|
272
|
+
|
273
|
+
let(:response_body) do
|
274
|
+
{
|
275
|
+
hostname => {
|
276
|
+
'template' => 'ubuntu-1604-x86_64',
|
277
|
+
'lifetime' => 12,
|
278
|
+
'running' => 9.66,
|
279
|
+
'state' => 'running',
|
280
|
+
'ip' => '127.0.0.1',
|
281
|
+
'domain' => domain,
|
282
|
+
}
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
let(:default_output) { "- #{fqdn} (running, ubuntu-1604-x86_64, 9.66/12 hours)" }
|
287
|
+
|
288
|
+
it 'prints output with host fqdn, template and duration info' do
|
289
|
+
expect(STDOUT).to receive(:puts).with(default_output)
|
290
|
+
|
291
|
+
subject
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'when tags are supplied' do
|
295
|
+
let(:hostname) { 'aiydvzpg23r415q' }
|
296
|
+
let(:response_body) do
|
297
|
+
{
|
298
|
+
hostname => {
|
299
|
+
'template' => 'redhat-7-x86_64',
|
300
|
+
'lifetime' => 48,
|
301
|
+
'running' => 7.67,
|
302
|
+
'state' => 'running',
|
303
|
+
'tags' => {
|
304
|
+
'user' => 'bob',
|
305
|
+
'role' => 'agent',
|
306
|
+
},
|
307
|
+
'ip' => '127.0.0.1',
|
308
|
+
'domain' => domain,
|
309
|
+
}
|
310
|
+
}
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'prints output with host fqdn, template, duration info, and tags' do
|
314
|
+
output = "- #{fqdn} (running, redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
|
315
|
+
|
316
|
+
expect(STDOUT).to receive(:puts).with(output)
|
317
|
+
|
318
|
+
subject
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context 'when print_to_stderr option is true' do
|
323
|
+
let(:print_to_stderr) { true }
|
324
|
+
|
325
|
+
it 'outputs to stderr instead of stdout' do
|
326
|
+
expect(STDERR).to receive(:puts).with(default_output)
|
327
|
+
|
328
|
+
subject
|
329
|
+
end
|
330
|
+
end
|
227
331
|
end
|
228
332
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
333
|
+
describe 'with nonstandard pooler service' do
|
334
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
|
335
|
+
|
336
|
+
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
|
337
|
+
let(:response_body) do
|
338
|
+
{
|
339
|
+
hostname => {
|
340
|
+
'fqdn' => hostname,
|
341
|
+
'os_triple' => 'solaris-11-sparc',
|
342
|
+
'reserved_by_user' => 'first.last',
|
343
|
+
'reserved_for_reason' => '',
|
344
|
+
'hours_left_on_reservation' => 35.89,
|
345
|
+
}
|
346
|
+
}
|
347
|
+
end
|
348
|
+
|
349
|
+
let(:default_output) { "- #{hostname} (solaris-11-sparc, 35.89h remaining)" }
|
350
|
+
|
351
|
+
it 'prints output with host, template, and time remaining' do
|
352
|
+
expect(STDOUT).to receive(:puts).with(default_output)
|
353
|
+
|
354
|
+
subject
|
355
|
+
end
|
356
|
+
|
357
|
+
context 'when reason is supplied' do
|
358
|
+
let(:response_body) do
|
359
|
+
{
|
360
|
+
hostname => {
|
361
|
+
'fqdn' => hostname,
|
362
|
+
'os_triple' => 'solaris-11-sparc',
|
363
|
+
'reserved_by_user' => 'first.last',
|
364
|
+
'reserved_for_reason' => 'testing',
|
365
|
+
'hours_left_on_reservation' => 35.89,
|
366
|
+
}
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'prints output with host, template, time remaining, and reason' do
|
371
|
+
output = '- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)'
|
372
|
+
|
373
|
+
expect(STDOUT).to receive(:puts).with(output)
|
374
|
+
|
375
|
+
subject
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
context 'when print_to_stderr option is true' do
|
380
|
+
let(:print_to_stderr) { true }
|
381
|
+
|
382
|
+
it 'outputs to stderr instead of stdout' do
|
383
|
+
expect(STDERR).to receive(:puts).with(default_output)
|
384
|
+
|
385
|
+
subject
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
239
389
|
|
240
|
-
|
390
|
+
describe 'with ABS service' do
|
391
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
392
|
+
|
393
|
+
let(:hostname) { '1597952189390' }
|
394
|
+
let(:fqdn) { 'example-noun.delivery.mycompany.net' }
|
395
|
+
let(:fqdn_hostname) {'example-noun'}
|
396
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
397
|
+
|
398
|
+
# This seems to be the miminal stub response from ABS for the current output
|
399
|
+
let(:response_body) do
|
400
|
+
{
|
401
|
+
hostname => {
|
402
|
+
'state' => 'allocated',
|
403
|
+
'allocated_resources' => [
|
404
|
+
{
|
405
|
+
'hostname' => fqdn,
|
406
|
+
'type' => template,
|
407
|
+
'engine' => 'vmpooler',
|
408
|
+
},
|
409
|
+
],
|
410
|
+
'request' => {
|
411
|
+
'job' => {
|
412
|
+
'id' => hostname,
|
413
|
+
}
|
414
|
+
},
|
415
|
+
}
|
416
|
+
}
|
417
|
+
end
|
418
|
+
|
419
|
+
# The vmpooler response contains metadata that is printed
|
420
|
+
let(:domain) { 'delivery.mycompany.net' }
|
421
|
+
let(:response_body_vmpooler) do
|
422
|
+
{
|
423
|
+
fqdn_hostname => {
|
424
|
+
'template' => template,
|
425
|
+
'lifetime' => 48,
|
426
|
+
'running' => 7.67,
|
427
|
+
'state' => 'running',
|
428
|
+
'tags' => {
|
429
|
+
'user' => 'bob',
|
430
|
+
'role' => 'agent',
|
431
|
+
},
|
432
|
+
'ip' => '127.0.0.1',
|
433
|
+
'domain' => domain,
|
434
|
+
}
|
435
|
+
}
|
436
|
+
end
|
437
|
+
|
438
|
+
before(:each) do
|
439
|
+
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
|
440
|
+
'url' => 'http://vmpooler.example.com',
|
441
|
+
'token' => 'krypto-knight'
|
442
|
+
})
|
443
|
+
allow(service).to receive(:query)
|
444
|
+
.with(anything, fqdn_hostname)
|
445
|
+
.and_return(response_body_vmpooler)
|
446
|
+
end
|
447
|
+
|
448
|
+
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
|
449
|
+
let(:default_output_second_line) { " - #{fqdn} (#{template})" }
|
450
|
+
|
451
|
+
it 'prints output with job id, host, and template' do
|
452
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
453
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line)
|
454
|
+
|
455
|
+
subject
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'prints more information when vmpooler_fallback is set output with job id, host, template, lifetime, user and role' do
|
459
|
+
fallback = {'vmpooler_fallback' => 'vmpooler'}
|
460
|
+
service.config.merge! fallback
|
461
|
+
default_output_second_line=" - #{fqdn} (running, #{template}, 7.67/48 hours, user: bob, role: agent)"
|
462
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
463
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line)
|
464
|
+
|
465
|
+
subject
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'prints in red when destroyed' do
|
469
|
+
fallback = {'vmpooler_fallback' => 'vmpooler'}
|
470
|
+
service.config.merge! fallback
|
471
|
+
response_body_vmpooler[fqdn_hostname]['state'] = "destroyed"
|
472
|
+
default_output_second_line_red=" - #{fqdn} (destroyed, #{template}, 7.67/48 hours, user: bob, role: agent)".red
|
473
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
474
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line_red)
|
475
|
+
|
476
|
+
subject
|
477
|
+
end
|
478
|
+
|
479
|
+
context 'when print_to_stderr option is true' do
|
480
|
+
let(:print_to_stderr) { true }
|
481
|
+
|
482
|
+
it 'outputs to stderr instead of stdout' do
|
483
|
+
expect(STDERR).to receive(:puts).with(default_output_first_line)
|
484
|
+
expect(STDERR).to receive(:puts).with(default_output_second_line)
|
485
|
+
|
486
|
+
subject
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
241
490
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
491
|
+
describe 'with ABS service returning vmpooler and nspooler resources' do
|
492
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
493
|
+
|
494
|
+
let(:hostname) { '1597952189390' }
|
495
|
+
let(:fqdn) { 'this-noun.delivery.mycompany.net' }
|
496
|
+
let(:fqdn_ns) { 'that-noun.delivery.mycompany.net' }
|
497
|
+
let(:fqdn_hostname) {'this-noun'}
|
498
|
+
let(:fqdn_ns_hostname) {'that-noun'}
|
499
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
500
|
+
let(:template_ns) { 'solaris-10-sparc' }
|
501
|
+
|
502
|
+
# This seems to be the miminal stub response from ABS for the current output
|
503
|
+
let(:response_body) do
|
504
|
+
{
|
505
|
+
hostname => {
|
506
|
+
'state' => 'allocated',
|
507
|
+
'allocated_resources' => [
|
508
|
+
{
|
509
|
+
'hostname' => fqdn,
|
510
|
+
'type' => template,
|
511
|
+
'engine' => 'vmpooler',
|
512
|
+
},
|
513
|
+
{
|
514
|
+
'hostname' => fqdn_ns,
|
515
|
+
'type' => template_ns,
|
516
|
+
'engine' => 'nspooler',
|
517
|
+
},
|
518
|
+
],
|
519
|
+
'request' => {
|
520
|
+
'job' => {
|
521
|
+
'id' => hostname,
|
522
|
+
}
|
523
|
+
},
|
524
|
+
}
|
525
|
+
}
|
526
|
+
end
|
527
|
+
|
528
|
+
# The vmpooler response contains metadata that is printed
|
529
|
+
let(:domain) { 'delivery.mycompany.net' }
|
530
|
+
let(:response_body_vmpooler) do
|
531
|
+
{
|
532
|
+
fqdn_hostname => {
|
533
|
+
'template' => template,
|
534
|
+
'lifetime' => 48,
|
535
|
+
'running' => 7.67,
|
536
|
+
'state' => 'running',
|
537
|
+
'tags' => {
|
538
|
+
'user' => 'bob',
|
539
|
+
'role' => 'agent',
|
540
|
+
},
|
541
|
+
'ip' => '127.0.0.1',
|
542
|
+
'domain' => domain,
|
543
|
+
}
|
544
|
+
}
|
545
|
+
end
|
546
|
+
|
547
|
+
before(:each) do
|
548
|
+
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
|
549
|
+
'url' => 'http://vmpooler.example.com',
|
550
|
+
'token' => 'krypto-knight'
|
551
|
+
})
|
552
|
+
allow(service).to receive(:query)
|
553
|
+
.with(anything, fqdn_hostname)
|
554
|
+
.and_return(response_body_vmpooler)
|
555
|
+
end
|
556
|
+
|
557
|
+
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
|
558
|
+
let(:default_output_second_line) { " - #{fqdn} (#{template})" }
|
559
|
+
let(:default_output_third_line) { " - #{fqdn_ns} (#{template_ns})" }
|
560
|
+
|
561
|
+
it 'prints output with job id, host, and template' do
|
562
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
563
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line)
|
564
|
+
expect(STDOUT).to receive(:puts).with(default_output_third_line)
|
565
|
+
|
566
|
+
subject
|
567
|
+
end
|
568
|
+
|
569
|
+
context 'when print_to_stderr option is true' do
|
570
|
+
let(:print_to_stderr) { true }
|
571
|
+
|
572
|
+
it 'outputs to stderr instead of stdout' do
|
573
|
+
expect(STDERR).to receive(:puts).with(default_output_first_line)
|
574
|
+
expect(STDERR).to receive(:puts).with(default_output_second_line)
|
575
|
+
expect(STDERR).to receive(:puts).with(default_output_third_line)
|
576
|
+
|
577
|
+
subject
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
246
582
|
|
247
|
-
|
583
|
+
describe '#get_vmpooler_service_config' do
|
584
|
+
let(:Conf) { double }
|
585
|
+
it 'returns an error if the vmpooler_fallback is not setup' do
|
586
|
+
config = {
|
587
|
+
'user' => 'foo',
|
588
|
+
'services' => {
|
589
|
+
'myabs' => {
|
590
|
+
'url' => 'http://abs.com',
|
591
|
+
'token' => 'krypto-night',
|
592
|
+
'type' => 'abs'
|
593
|
+
}
|
594
|
+
}
|
595
|
+
}
|
596
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
597
|
+
expect{Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])}.to raise_error(ArgumentError)
|
598
|
+
end
|
599
|
+
it 'returns an error if the vmpooler_fallback is setup but cannot be found' do
|
600
|
+
config = {
|
601
|
+
'user' => 'foo',
|
602
|
+
'services' => {
|
603
|
+
'myabs' => {
|
604
|
+
'url' => 'http://abs.com',
|
605
|
+
'token' => 'krypto-night',
|
606
|
+
'type' => 'abs',
|
607
|
+
'vmpooler_fallback' => 'myvmpooler'
|
608
|
+
}
|
609
|
+
}
|
610
|
+
}
|
611
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
612
|
+
expect{Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])}.to raise_error(ArgumentError, /myvmpooler/)
|
613
|
+
end
|
614
|
+
it 'returns the vmpooler_fallback config' do
|
615
|
+
config = {
|
616
|
+
'user' => 'foo',
|
617
|
+
'services' => {
|
618
|
+
'myabs' => {
|
619
|
+
'url' => 'http://abs.com',
|
620
|
+
'token' => 'krypto-night',
|
621
|
+
'type' => 'abs',
|
622
|
+
'vmpooler_fallback' => 'myvmpooler'
|
623
|
+
},
|
624
|
+
'myvmpooler' => {
|
625
|
+
'url' => 'http://vmpooler.com',
|
626
|
+
'token' => 'krypto-knight'
|
627
|
+
}
|
628
|
+
}
|
629
|
+
}
|
630
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
631
|
+
expect(Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])).to include({
|
632
|
+
'url' => 'http://vmpooler.com',
|
633
|
+
'token' => 'krypto-knight',
|
634
|
+
'user' => 'foo',
|
635
|
+
'type' => 'vmpooler'
|
636
|
+
})
|
248
637
|
end
|
249
638
|
end
|
250
639
|
end
|