vmfloaty 0.11.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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