vmfloaty 0.11.1 → 1.0.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 +2 -2
- data/extras/completions/floaty.zsh +37 -0
- data/lib/vmfloaty.rb +92 -7
- data/lib/vmfloaty/abs.rb +118 -49
- data/lib/vmfloaty/conf.rb +1 -1
- data/lib/vmfloaty/service.rb +20 -0
- data/lib/vmfloaty/utils.rb +79 -11
- data/lib/vmfloaty/version.rb +2 -1
- data/spec/vmfloaty/abs_spec.rb +52 -5
- data/spec/vmfloaty/utils_spec.rb +436 -73
- metadata +11 -10
data/lib/vmfloaty/conf.rb
CHANGED
data/lib/vmfloaty/service.rb
CHANGED
@@ -7,11 +7,13 @@ require 'vmfloaty/ssh'
|
|
7
7
|
|
8
8
|
class Service
|
9
9
|
attr_reader :config
|
10
|
+
attr_accessor :silent
|
10
11
|
|
11
12
|
def initialize(options, config_hash = {})
|
12
13
|
options ||= Commander::Command::Options.new
|
13
14
|
@config = Utils.get_service_config config_hash, options
|
14
15
|
@service_object = Utils.get_service_object @config['type']
|
16
|
+
@silent = false
|
15
17
|
end
|
16
18
|
|
17
19
|
def method_missing(method_name, *args, &block)
|
@@ -103,6 +105,7 @@ class Service
|
|
103
105
|
end
|
104
106
|
|
105
107
|
def modify(verbose, hostname, modify_hash)
|
108
|
+
maybe_use_vmpooler
|
106
109
|
@service_object.modify verbose, url, hostname, token, modify_hash
|
107
110
|
end
|
108
111
|
|
@@ -115,18 +118,35 @@ class Service
|
|
115
118
|
end
|
116
119
|
|
117
120
|
def summary(verbose)
|
121
|
+
maybe_use_vmpooler
|
118
122
|
@service_object.summary verbose, url
|
119
123
|
end
|
120
124
|
|
121
125
|
def snapshot(verbose, hostname)
|
126
|
+
maybe_use_vmpooler
|
122
127
|
@service_object.snapshot verbose, url, hostname, token
|
123
128
|
end
|
124
129
|
|
125
130
|
def revert(verbose, hostname, snapshot_sha)
|
131
|
+
maybe_use_vmpooler
|
126
132
|
@service_object.revert verbose, url, hostname, token, snapshot_sha
|
127
133
|
end
|
128
134
|
|
129
135
|
def disk(verbose, hostname, disk)
|
136
|
+
maybe_use_vmpooler
|
130
137
|
@service_object.disk(verbose, url, hostname, token, disk)
|
131
138
|
end
|
139
|
+
|
140
|
+
# some methods do not exist for ABS, and if possible should target the Pooler service
|
141
|
+
def maybe_use_vmpooler
|
142
|
+
if @service_object.is_a?(ABS.class)
|
143
|
+
if !self.silent
|
144
|
+
FloatyLogger.info "The service in use is ABS, but the requested method should run against vmpooler directly, using fallback_vmpooler config from ~/.vmfloaty.yml"
|
145
|
+
self.silent = true
|
146
|
+
end
|
147
|
+
|
148
|
+
@config = Utils.get_vmpooler_service_config(@config['vmpooler_fallback'])
|
149
|
+
@service_object = Pooler
|
150
|
+
end
|
151
|
+
end
|
132
152
|
end
|
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,27 +84,60 @@ 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
|
+
|
82
109
|
fetched_data = self.get_host_data(verbose, service, hostnames)
|
83
110
|
fetched_data.each do |hostname, host_data|
|
84
111
|
case service.type
|
85
112
|
when 'ABS'
|
86
|
-
|
87
|
-
|
88
|
-
|
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"
|
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
|
89
128
|
end
|
90
129
|
when 'Pooler'
|
91
130
|
tag_pairs = []
|
92
131
|
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
93
132
|
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
94
133
|
metadata = [host_data['template'], duration, *tag_pairs]
|
95
|
-
puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})"
|
134
|
+
output_target.puts "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
|
96
135
|
when 'NonstandardPooler'
|
97
136
|
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
98
137
|
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
99
138
|
line += ", reason: #{host_data['reserved_for_reason']}" unless host_data['reserved_for_reason'].empty?
|
100
139
|
line += ')'
|
101
|
-
puts line
|
140
|
+
output_target.puts line
|
102
141
|
else
|
103
142
|
raise "Invalid service type #{service.type}"
|
104
143
|
end
|
@@ -195,12 +234,15 @@ class Utils
|
|
195
234
|
end
|
196
235
|
|
197
236
|
def self.get_service_object(type = '')
|
198
|
-
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
199
237
|
abs_strings = %w[abs alwaysbescheduling always_be_scheduling]
|
200
|
-
|
201
|
-
|
202
|
-
|
238
|
+
nspooler_strings = %w[ns nspooler nonstandard nonstandard_pooler]
|
239
|
+
vmpooler_strings = %w[vmpooler]
|
240
|
+
if abs_strings.include? type.downcase
|
203
241
|
ABS
|
242
|
+
elsif nspooler_strings.include? type.downcase
|
243
|
+
NonstandardPooler
|
244
|
+
elsif vmpooler_strings.include? type.downcase
|
245
|
+
Pooler
|
204
246
|
else
|
205
247
|
Pooler
|
206
248
|
end
|
@@ -241,4 +283,30 @@ class Utils
|
|
241
283
|
|
242
284
|
service_config
|
243
285
|
end
|
286
|
+
|
287
|
+
# This method gets the vmpooler service configured in ~/.vmfloaty
|
288
|
+
def self.get_vmpooler_service_config(vmpooler_fallback)
|
289
|
+
config = Conf.read_config
|
290
|
+
# The top-level url, user, and token values in the config file are treated as defaults
|
291
|
+
service_config = {
|
292
|
+
'url' => config['url'],
|
293
|
+
'user' => config['user'],
|
294
|
+
'token' => config['token'],
|
295
|
+
'type' => 'vmpooler',
|
296
|
+
}
|
297
|
+
|
298
|
+
# at a minimum, the url needs to be configured
|
299
|
+
if config['services'] && config['services'][vmpooler_fallback] && config['services'][vmpooler_fallback]['url']
|
300
|
+
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
301
|
+
service_config.merge! config['services'][vmpooler_fallback]
|
302
|
+
else
|
303
|
+
if vmpooler_fallback.nil?
|
304
|
+
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'"
|
305
|
+
else
|
306
|
+
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'"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
service_config
|
311
|
+
end
|
244
312
|
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/status/platforms/vmpooler").
|
15
|
+
to_return(:status => 200, :body => "", :headers => {})
|
16
|
+
stub_request(:get, "http://foo/status/platforms/ondemand_vmpooler").
|
17
|
+
to_return(:status => 200, :body => "", :headers => {})
|
18
|
+
stub_request(:get, "http://foo/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/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/status/platforms/vmpooler").
|
39
|
+
to_return(:status => 200, :body => "", :headers => {})
|
40
|
+
stub_request(:get, "http://foo/status/platforms/ondemand_vmpooler").
|
41
|
+
to_return(:status => 200, :body => "", :headers => {})
|
42
|
+
stub_request(:get, "http://foo/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/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'
|
@@ -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
|
data/spec/vmfloaty/utils_spec.rb
CHANGED
@@ -77,9 +77,17 @@ describe Utils do
|
|
77
77
|
expect(Utils.get_service_object).to be Pooler
|
78
78
|
end
|
79
79
|
|
80
|
+
it 'uses abs when told explicitly' do
|
81
|
+
expect(Utils.get_service_object('abs')).to be ABS
|
82
|
+
end
|
83
|
+
|
80
84
|
it 'uses nspooler when told explicitly' do
|
81
85
|
expect(Utils.get_service_object('nspooler')).to be NonstandardPooler
|
82
86
|
end
|
87
|
+
|
88
|
+
it 'uses vmpooler when told explicitly' do
|
89
|
+
expect(Utils.get_service_object('vmpooler')).to be Pooler
|
90
|
+
end
|
83
91
|
end
|
84
92
|
|
85
93
|
describe '#get_service_config' do
|
@@ -154,97 +162,452 @@ describe Utils do
|
|
154
162
|
end
|
155
163
|
end
|
156
164
|
|
157
|
-
describe '#
|
165
|
+
describe '#print_fqdn_for_host' do
|
158
166
|
let(:url) { 'http://pooler.example.com' }
|
159
167
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
168
|
+
subject { Utils.print_fqdn_for_host(service, hostname, host_data) }
|
169
|
+
|
170
|
+
describe 'with vmpooler host' do
|
171
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url) }
|
172
|
+
let(:hostname) { 'mcpy42eqjxli9g2' }
|
173
|
+
let(:domain) { 'delivery.mycompany.net' }
|
174
|
+
let(:fqdn) { [hostname, domain].join('.') }
|
175
|
+
|
176
|
+
let(:host_data) do
|
177
|
+
{
|
178
|
+
'template' => 'ubuntu-1604-x86_64',
|
179
|
+
'lifetime' => 12,
|
180
|
+
'running' => 9.66,
|
181
|
+
'state' => 'running',
|
182
|
+
'ip' => '127.0.0.1',
|
183
|
+
'domain' => domain,
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'outputs fqdn for host' do
|
188
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
189
|
+
|
190
|
+
subject
|
191
|
+
end
|
180
192
|
end
|
181
193
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
'
|
191
|
-
'
|
192
|
-
}
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
describe 'with nonstandard pooler host' do
|
195
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
|
196
|
+
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
|
197
|
+
let(:host_data) do
|
198
|
+
{
|
199
|
+
'fqdn' => hostname,
|
200
|
+
'os_triple' => 'solaris-11-sparc',
|
201
|
+
'reserved_by_user' => 'first.last',
|
202
|
+
'reserved_for_reason' => '',
|
203
|
+
'hours_left_on_reservation' => 35.89,
|
204
|
+
}
|
205
|
+
end
|
206
|
+
let(:fqdn) { hostname } # for nspooler these are the same
|
207
|
+
|
208
|
+
it 'outputs fqdn for host' do
|
209
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
210
|
+
|
211
|
+
subject
|
212
|
+
end
|
213
|
+
end
|
197
214
|
|
198
|
-
|
215
|
+
describe 'with ABS host' do
|
216
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
217
|
+
let(:hostname) { '1597952189390' }
|
218
|
+
let(:fqdn) { 'example-noun.delivery.puppetlabs.net' }
|
219
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
220
|
+
|
221
|
+
# This seems to be the miminal stub response from ABS for the current output
|
222
|
+
let(:host_data) do
|
223
|
+
{
|
224
|
+
'state' => 'allocated',
|
225
|
+
'allocated_resources' => [
|
226
|
+
{
|
227
|
+
'hostname' => fqdn,
|
228
|
+
'type' => template,
|
229
|
+
'enging' => 'vmpooler',
|
230
|
+
},
|
231
|
+
],
|
232
|
+
'request' => {
|
233
|
+
'job' => {
|
234
|
+
'id' => hostname,
|
235
|
+
}
|
236
|
+
},
|
237
|
+
}
|
238
|
+
end
|
199
239
|
|
200
|
-
|
201
|
-
|
202
|
-
.with(nil, hostname)
|
203
|
-
.and_return(response_body)
|
240
|
+
it 'outputs fqdn for host' do
|
241
|
+
expect(STDOUT).to receive(:puts).with(fqdn)
|
204
242
|
|
205
|
-
|
243
|
+
subject
|
244
|
+
end
|
206
245
|
end
|
246
|
+
end
|
207
247
|
|
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)
|
248
|
+
describe '#pretty_print_hosts' do
|
249
|
+
let(:url) { 'http://pooler.example.com' }
|
250
|
+
let(:verbose) { nil }
|
251
|
+
let(:print_to_stderr) { false }
|
220
252
|
|
221
|
-
|
253
|
+
before(:each) do
|
222
254
|
allow(service).to receive(:query)
|
223
|
-
.with(
|
255
|
+
.with(anything, hostname)
|
224
256
|
.and_return(response_body)
|
257
|
+
end
|
225
258
|
|
226
|
-
|
259
|
+
subject { Utils.pretty_print_hosts(verbose, service, hostname, print_to_stderr) }
|
260
|
+
|
261
|
+
describe 'with vmpooler service' do
|
262
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url) }
|
263
|
+
|
264
|
+
let(:hostname) { 'mcpy42eqjxli9g2' }
|
265
|
+
let(:domain) { 'delivery.mycompany.net' }
|
266
|
+
let(:fqdn) { [hostname, domain].join('.') }
|
267
|
+
|
268
|
+
let(:response_body) do
|
269
|
+
{
|
270
|
+
hostname => {
|
271
|
+
'template' => 'ubuntu-1604-x86_64',
|
272
|
+
'lifetime' => 12,
|
273
|
+
'running' => 9.66,
|
274
|
+
'state' => 'running',
|
275
|
+
'ip' => '127.0.0.1',
|
276
|
+
'domain' => domain,
|
277
|
+
}
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
let(:default_output) { "- #{fqdn} (ubuntu-1604-x86_64, 9.66/12 hours)" }
|
282
|
+
|
283
|
+
it 'prints output with host fqdn, template and duration info' do
|
284
|
+
expect(STDOUT).to receive(:puts).with(default_output)
|
285
|
+
|
286
|
+
subject
|
287
|
+
end
|
288
|
+
|
289
|
+
context 'when tags are supplied' do
|
290
|
+
let(:hostname) { 'aiydvzpg23r415q' }
|
291
|
+
let(:response_body) do
|
292
|
+
{
|
293
|
+
hostname => {
|
294
|
+
'template' => 'redhat-7-x86_64',
|
295
|
+
'lifetime' => 48,
|
296
|
+
'running' => 7.67,
|
297
|
+
'state' => 'running',
|
298
|
+
'tags' => {
|
299
|
+
'user' => 'bob',
|
300
|
+
'role' => 'agent',
|
301
|
+
},
|
302
|
+
'ip' => '127.0.0.1',
|
303
|
+
'domain' => domain,
|
304
|
+
}
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'prints output with host fqdn, template, duration info, and tags' do
|
309
|
+
output = "- #{fqdn} (redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)"
|
310
|
+
|
311
|
+
expect(STDOUT).to receive(:puts).with(output)
|
312
|
+
|
313
|
+
subject
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'when print_to_stderr option is true' do
|
318
|
+
let(:print_to_stderr) { true }
|
319
|
+
|
320
|
+
it 'outputs to stderr instead of stdout' do
|
321
|
+
expect(STDERR).to receive(:puts).with(default_output)
|
322
|
+
|
323
|
+
subject
|
324
|
+
end
|
325
|
+
end
|
227
326
|
end
|
228
327
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
328
|
+
describe 'with nonstandard pooler service' do
|
329
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'ns') }
|
330
|
+
|
331
|
+
let(:hostname) { 'sol11-9.delivery.mycompany.net' }
|
332
|
+
let(:response_body) do
|
333
|
+
{
|
334
|
+
hostname => {
|
335
|
+
'fqdn' => hostname,
|
336
|
+
'os_triple' => 'solaris-11-sparc',
|
337
|
+
'reserved_by_user' => 'first.last',
|
338
|
+
'reserved_for_reason' => '',
|
339
|
+
'hours_left_on_reservation' => 35.89,
|
340
|
+
}
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
let(:default_output) { "- #{hostname} (solaris-11-sparc, 35.89h remaining)" }
|
345
|
+
|
346
|
+
it 'prints output with host, template, and time remaining' do
|
347
|
+
expect(STDOUT).to receive(:puts).with(default_output)
|
348
|
+
|
349
|
+
subject
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'when reason is supplied' do
|
353
|
+
let(:response_body) do
|
354
|
+
{
|
355
|
+
hostname => {
|
356
|
+
'fqdn' => hostname,
|
357
|
+
'os_triple' => 'solaris-11-sparc',
|
358
|
+
'reserved_by_user' => 'first.last',
|
359
|
+
'reserved_for_reason' => 'testing',
|
360
|
+
'hours_left_on_reservation' => 35.89,
|
361
|
+
}
|
362
|
+
}
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'prints output with host, template, time remaining, and reason' do
|
366
|
+
output = '- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)'
|
367
|
+
|
368
|
+
expect(STDOUT).to receive(:puts).with(output)
|
369
|
+
|
370
|
+
subject
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'when print_to_stderr option is true' do
|
375
|
+
let(:print_to_stderr) { true }
|
376
|
+
|
377
|
+
it 'outputs to stderr instead of stdout' do
|
378
|
+
expect(STDERR).to receive(:puts).with(default_output)
|
379
|
+
|
380
|
+
subject
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
239
384
|
|
240
|
-
|
385
|
+
describe 'with ABS service' do
|
386
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
387
|
+
|
388
|
+
let(:hostname) { '1597952189390' }
|
389
|
+
let(:fqdn) { 'example-noun.delivery.mycompany.net' }
|
390
|
+
let(:fqdn_hostname) {'example-noun'}
|
391
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
392
|
+
|
393
|
+
# This seems to be the miminal stub response from ABS for the current output
|
394
|
+
let(:response_body) do
|
395
|
+
{
|
396
|
+
hostname => {
|
397
|
+
'state' => 'allocated',
|
398
|
+
'allocated_resources' => [
|
399
|
+
{
|
400
|
+
'hostname' => fqdn,
|
401
|
+
'type' => template,
|
402
|
+
'engine' => 'vmpooler',
|
403
|
+
},
|
404
|
+
],
|
405
|
+
'request' => {
|
406
|
+
'job' => {
|
407
|
+
'id' => hostname,
|
408
|
+
}
|
409
|
+
},
|
410
|
+
}
|
411
|
+
}
|
412
|
+
end
|
413
|
+
|
414
|
+
# The vmpooler response contains metadata that is printed
|
415
|
+
let(:domain) { 'delivery.mycompany.net' }
|
416
|
+
let(:response_body_vmpooler) do
|
417
|
+
{
|
418
|
+
fqdn_hostname => {
|
419
|
+
'template' => template,
|
420
|
+
'lifetime' => 48,
|
421
|
+
'running' => 7.67,
|
422
|
+
'state' => 'running',
|
423
|
+
'tags' => {
|
424
|
+
'user' => 'bob',
|
425
|
+
'role' => 'agent',
|
426
|
+
},
|
427
|
+
'ip' => '127.0.0.1',
|
428
|
+
'domain' => domain,
|
429
|
+
}
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
before(:each) do
|
434
|
+
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
|
435
|
+
'url' => 'http://vmpooler.example.com',
|
436
|
+
'token' => 'krypto-knight'
|
437
|
+
})
|
438
|
+
allow(service).to receive(:query)
|
439
|
+
.with(anything, fqdn_hostname)
|
440
|
+
.and_return(response_body_vmpooler)
|
441
|
+
end
|
442
|
+
|
443
|
+
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
|
444
|
+
let(:default_output_second_line) { " - #{fqdn} (#{template}, 7.67/48 hours, user: bob, role: agent)" }
|
445
|
+
|
446
|
+
it 'prints output with job id, host, and template' do
|
447
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
448
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line)
|
449
|
+
|
450
|
+
subject
|
451
|
+
end
|
452
|
+
|
453
|
+
context 'when print_to_stderr option is true' do
|
454
|
+
let(:print_to_stderr) { true }
|
455
|
+
|
456
|
+
it 'outputs to stderr instead of stdout' do
|
457
|
+
expect(STDERR).to receive(:puts).with(default_output_first_line)
|
458
|
+
expect(STDERR).to receive(:puts).with(default_output_second_line)
|
459
|
+
|
460
|
+
subject
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
241
464
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
465
|
+
describe 'with ABS service returning vmpooler and nspooler resources' do
|
466
|
+
let(:service) { Service.new(MockOptions.new, 'url' => url, 'type' => 'abs') }
|
467
|
+
|
468
|
+
let(:hostname) { '1597952189390' }
|
469
|
+
let(:fqdn) { 'this-noun.delivery.mycompany.net' }
|
470
|
+
let(:fqdn_ns) { 'that-noun.delivery.mycompany.net' }
|
471
|
+
let(:fqdn_hostname) {'this-noun'}
|
472
|
+
let(:fqdn_ns_hostname) {'that-noun'}
|
473
|
+
let(:template) { 'ubuntu-1604-x86_64' }
|
474
|
+
let(:template_ns) { 'solaris-10-sparc' }
|
475
|
+
|
476
|
+
# This seems to be the miminal stub response from ABS for the current output
|
477
|
+
let(:response_body) do
|
478
|
+
{
|
479
|
+
hostname => {
|
480
|
+
'state' => 'allocated',
|
481
|
+
'allocated_resources' => [
|
482
|
+
{
|
483
|
+
'hostname' => fqdn,
|
484
|
+
'type' => template,
|
485
|
+
'engine' => 'vmpooler',
|
486
|
+
},
|
487
|
+
{
|
488
|
+
'hostname' => fqdn_ns,
|
489
|
+
'type' => template_ns,
|
490
|
+
'engine' => 'nspooler',
|
491
|
+
},
|
492
|
+
],
|
493
|
+
'request' => {
|
494
|
+
'job' => {
|
495
|
+
'id' => hostname,
|
496
|
+
}
|
497
|
+
},
|
498
|
+
}
|
499
|
+
}
|
500
|
+
end
|
501
|
+
|
502
|
+
# The vmpooler response contains metadata that is printed
|
503
|
+
let(:domain) { 'delivery.mycompany.net' }
|
504
|
+
let(:response_body_vmpooler) do
|
505
|
+
{
|
506
|
+
fqdn_hostname => {
|
507
|
+
'template' => template,
|
508
|
+
'lifetime' => 48,
|
509
|
+
'running' => 7.67,
|
510
|
+
'state' => 'running',
|
511
|
+
'tags' => {
|
512
|
+
'user' => 'bob',
|
513
|
+
'role' => 'agent',
|
514
|
+
},
|
515
|
+
'ip' => '127.0.0.1',
|
516
|
+
'domain' => domain,
|
517
|
+
}
|
518
|
+
}
|
519
|
+
end
|
520
|
+
|
521
|
+
before(:each) do
|
522
|
+
allow(Utils).to receive(:get_vmpooler_service_config).and_return({
|
523
|
+
'url' => 'http://vmpooler.example.com',
|
524
|
+
'token' => 'krypto-knight'
|
525
|
+
})
|
526
|
+
allow(service).to receive(:query)
|
527
|
+
.with(anything, fqdn_hostname)
|
528
|
+
.and_return(response_body_vmpooler)
|
529
|
+
end
|
530
|
+
|
531
|
+
let(:default_output_first_line) { "- [JobID:#{hostname}] <allocated>" }
|
532
|
+
let(:default_output_second_line) { " - #{fqdn} (#{template}, 7.67/48 hours, user: bob, role: agent)" }
|
533
|
+
let(:default_output_third_line) { " - #{fqdn_ns} (#{template_ns})" }
|
534
|
+
|
535
|
+
it 'prints output with job id, host, and template' do
|
536
|
+
expect(STDOUT).to receive(:puts).with(default_output_first_line)
|
537
|
+
expect(STDOUT).to receive(:puts).with(default_output_second_line)
|
538
|
+
expect(STDOUT).to receive(:puts).with(default_output_third_line)
|
539
|
+
|
540
|
+
subject
|
541
|
+
end
|
542
|
+
|
543
|
+
context 'when print_to_stderr option is true' do
|
544
|
+
let(:print_to_stderr) { true }
|
545
|
+
|
546
|
+
it 'outputs to stderr instead of stdout' do
|
547
|
+
expect(STDERR).to receive(:puts).with(default_output_first_line)
|
548
|
+
expect(STDERR).to receive(:puts).with(default_output_second_line)
|
549
|
+
expect(STDERR).to receive(:puts).with(default_output_third_line)
|
550
|
+
|
551
|
+
subject
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
246
556
|
|
247
|
-
|
557
|
+
describe '#get_vmpooler_service_config' do
|
558
|
+
let(:Conf) { double }
|
559
|
+
it 'returns an error if the vmpooler_fallback is not setup' do
|
560
|
+
config = {
|
561
|
+
'user' => 'foo',
|
562
|
+
'services' => {
|
563
|
+
'myabs' => {
|
564
|
+
'url' => 'http://abs.com',
|
565
|
+
'token' => 'krypto-night',
|
566
|
+
'type' => 'abs'
|
567
|
+
}
|
568
|
+
}
|
569
|
+
}
|
570
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
571
|
+
expect{Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])}.to raise_error(ArgumentError)
|
572
|
+
end
|
573
|
+
it 'returns an error if the vmpooler_fallback is setup but cannot be found' do
|
574
|
+
config = {
|
575
|
+
'user' => 'foo',
|
576
|
+
'services' => {
|
577
|
+
'myabs' => {
|
578
|
+
'url' => 'http://abs.com',
|
579
|
+
'token' => 'krypto-night',
|
580
|
+
'type' => 'abs',
|
581
|
+
'vmpooler_fallback' => 'myvmpooler'
|
582
|
+
}
|
583
|
+
}
|
584
|
+
}
|
585
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
586
|
+
expect{Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])}.to raise_error(ArgumentError, /myvmpooler/)
|
587
|
+
end
|
588
|
+
it 'returns the vmpooler_fallback config' do
|
589
|
+
config = {
|
590
|
+
'user' => 'foo',
|
591
|
+
'services' => {
|
592
|
+
'myabs' => {
|
593
|
+
'url' => 'http://abs.com',
|
594
|
+
'token' => 'krypto-night',
|
595
|
+
'type' => 'abs',
|
596
|
+
'vmpooler_fallback' => 'myvmpooler'
|
597
|
+
},
|
598
|
+
'myvmpooler' => {
|
599
|
+
'url' => 'http://vmpooler.com',
|
600
|
+
'token' => 'krypto-knight'
|
601
|
+
}
|
602
|
+
}
|
603
|
+
}
|
604
|
+
allow(Conf).to receive(:read_config).and_return(config)
|
605
|
+
expect(Utils.get_vmpooler_service_config(config['services']['myabs']['vmpooler_fallback'])).to include({
|
606
|
+
'url' => 'http://vmpooler.com',
|
607
|
+
'token' => 'krypto-knight',
|
608
|
+
'user' => 'foo',
|
609
|
+
'type' => 'vmpooler'
|
610
|
+
})
|
248
611
|
end
|
249
612
|
end
|
250
613
|
end
|