vmfloaty 0.11.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ class Conf
8
8
  begin
9
9
  conf = YAML.load_file("#{Dir.home}/.vmfloaty.yml")
10
10
  rescue StandardError
11
- FloatyLogger.warn "WARNING: There was no config file at #{Dir.home}/.vmfloaty.yml"
11
+ # ignore
12
12
  end
13
13
  conf
14
14
  end
@@ -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
@@ -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,27 +84,60 @@ 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
+
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
- # For ABS, 'hostname' variable is the jobID
87
- host_data['allocated_resources'].each do |vm_name, _i|
88
- puts "- [JobID:#{host_data['request']['job']['id']}] #{vm_name['hostname']} (#{vm_name['type']}) <#{host_data['state']}>"
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
- if nspooler_strings.include? type.downcase
201
- NonstandardPooler
202
- elsif abs_strings.include? type.downcase
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Vmfloaty
4
- VERSION = '0.11.1'
4
+ VERSION = '1.0.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/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
- "{ \"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'
@@ -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
@@ -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 '#pretty_print_hosts' do
165
+ describe '#print_fqdn_for_host' do
158
166
  let(:url) { 'http://pooler.example.com' }
159
167
 
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)
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
- 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)'
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
- expect(STDOUT).to receive(:puts).with(output)
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
- service = Service.new(MockOptions.new, 'url' => url)
201
- allow(service).to receive(:query)
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
- Utils.pretty_print_hosts(nil, service, hostname)
243
+ subject
244
+ end
206
245
  end
246
+ end
207
247
 
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)
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
- service = Service.new(MockOptions.new, 'url' => url, 'type' => 'ns')
253
+ before(:each) do
222
254
  allow(service).to receive(:query)
223
- .with(nil, hostname)
255
+ .with(anything, hostname)
224
256
  .and_return(response_body)
257
+ end
225
258
 
226
- Utils.pretty_print_hosts(nil, service, hostname)
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
- 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)'
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
- expect(STDOUT).to receive(:puts).with(output)
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
- service = Service.new(MockOptions.new, 'url' => url, 'type' => 'ns')
243
- allow(service).to receive(:query)
244
- .with(nil, hostname)
245
- .and_return(response_body)
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
- Utils.pretty_print_hosts(nil, service, hostname)
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