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.
@@ -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
- names.map { |name| "- #{name} (#{os})" }
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.pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false)
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
- case service.type
89
- when 'ABS'
90
- # For ABS, 'hostname' variable is the jobID
91
- if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
92
- host_data['allocated_resources'].each do |vm_name, _i|
93
- puts "- [JobID:#{host_data['request']['job']['id']}] #{vm_name['hostname']} (#{vm_name['type']}) <#{host_data['state']}>"
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
- if nspooler_strings.include? type.downcase
179
- NonstandardPooler
180
- elsif abs_strings.include? type.downcase
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Vmfloaty
4
- VERSION = '0.11.0'
4
+ VERSION = '1.3.0'
5
5
  end
6
+
@@ -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
- "{ \"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}}",
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
- "{\"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}}"
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
- "{ \"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}}"
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
 
@@ -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 '#pretty_print_hosts' do
170
+ describe '#print_fqdn_for_host' do
158
171
  let(:url) { 'http://pooler.example.com' }
159
172
 
160
- it 'prints a vmpooler output with host fqdn, template and duration info' do
161
- hostname = 'mcpy42eqjxli9g2'
162
- response_body = { hostname => {
163
- 'template' => 'ubuntu-1604-x86_64',
164
- 'lifetime' => 12,
165
- 'running' => 9.66,
166
- 'state' => 'running',
167
- 'ip' => '127.0.0.1',
168
- 'domain' => 'delivery.mycompany.net',
169
- } }
170
- output = '- mcpy42eqjxli9g2.delivery.mycompany.net (ubuntu-1604-x86_64, 9.66/12 hours)'
171
-
172
- expect(STDOUT).to receive(:puts).with(output)
173
-
174
- service = Service.new(MockOptions.new, 'url' => url)
175
- allow(service).to receive(:query)
176
- .with(nil, hostname)
177
- .and_return(response_body)
178
-
179
- Utils.pretty_print_hosts(nil, service, hostname)
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
- it 'prints a vmpooler output with host fqdn, template, duration info, and tags when supplied' do
183
- hostname = 'aiydvzpg23r415q'
184
- response_body = { hostname => {
185
- 'template' => 'redhat-7-x86_64',
186
- 'lifetime' => 48,
187
- 'running' => 7.67,
188
- 'state' => 'running',
189
- 'tags' => {
190
- 'user' => 'bob',
191
- 'role' => 'agent',
192
- },
193
- 'ip' => '127.0.0.1',
194
- 'domain' => 'delivery.mycompany.net',
195
- } }
196
- output = '- aiydvzpg23r415q.delivery.mycompany.net (redhat-7-x86_64, 7.67/48 hours, user: bob, role: agent)'
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
- expect(STDOUT).to receive(:puts).with(output)
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
- service = Service.new(MockOptions.new, 'url' => url)
201
- allow(service).to receive(:query)
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
- Utils.pretty_print_hosts(nil, service, hostname)
248
+ subject
249
+ end
206
250
  end
251
+ end
207
252
 
208
- it 'prints a nonstandard pooler output with host, template, and time remaining' do
209
- hostname = 'sol11-9.delivery.mycompany.net'
210
- response_body = { hostname => {
211
- 'fqdn' => hostname,
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
- service = Service.new(MockOptions.new, 'url' => url, 'type' => 'ns')
258
+ before(:each) do
222
259
  allow(service).to receive(:query)
223
- .with(nil, hostname)
260
+ .with(anything, hostname)
224
261
  .and_return(response_body)
262
+ end
225
263
 
226
- Utils.pretty_print_hosts(nil, service, hostname)
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
- it 'prints a nonstandard pooler output with host, template, time remaining, and reason' do
230
- hostname = 'sol11-9.delivery.mycompany.net'
231
- response_body = { hostname => {
232
- 'fqdn' => hostname,
233
- 'os_triple' => 'solaris-11-sparc',
234
- 'reserved_by_user' => 'first.last',
235
- 'reserved_for_reason' => 'testing',
236
- 'hours_left_on_reservation' => 35.89,
237
- } }
238
- output = '- sol11-9.delivery.mycompany.net (solaris-11-sparc, 35.89h remaining, reason: testing)'
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
- expect(STDOUT).to receive(:puts).with(output)
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
- service = Service.new(MockOptions.new, 'url' => url, 'type' => 'ns')
243
- allow(service).to receive(:query)
244
- .with(nil, hostname)
245
- .and_return(response_body)
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
- Utils.pretty_print_hosts(nil, service, hostname)
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