vmfloaty 0.11.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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