vmfloaty 1.0.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/extras/completions/floaty.bash +16 -8
- data/extras/completions/floaty.zsh +13 -7
- data/lib/vmfloaty.rb +62 -50
- data/lib/vmfloaty/abs.rb +86 -69
- data/lib/vmfloaty/http.rb +2 -6
- data/lib/vmfloaty/logger.rb +19 -3
- data/lib/vmfloaty/nonstandard_pooler.rb +3 -2
- data/lib/vmfloaty/pooler.rb +20 -25
- data/lib/vmfloaty/service.rb +6 -6
- data/lib/vmfloaty/utils.rb +67 -62
- data/lib/vmfloaty/version.rb +1 -2
- data/spec/spec_helper.rb +20 -3
- data/spec/vmfloaty/abs/auth_spec.rb +26 -17
- data/spec/vmfloaty/abs_spec.rb +55 -46
- data/spec/vmfloaty/auth_spec.rb +23 -13
- data/spec/vmfloaty/nonstandard_pooler_spec.rb +32 -31
- data/spec/vmfloaty/pooler_spec.rb +29 -26
- data/spec/vmfloaty/service_spec.rb +10 -10
- data/spec/vmfloaty/ssh_spec.rb +3 -3
- data/spec/vmfloaty/utils_spec.rb +191 -159
- metadata +17 -11
data/lib/vmfloaty/http.rb
CHANGED
@@ -21,13 +21,11 @@ class Http
|
|
21
21
|
|
22
22
|
url = "https://#{url}" unless url?(url)
|
23
23
|
|
24
|
-
|
24
|
+
Faraday.new(url: url, ssl: { verify: false }) do |faraday|
|
25
25
|
faraday.request :url_encoded
|
26
26
|
faraday.response :logger if verbose
|
27
27
|
faraday.adapter Faraday.default_adapter
|
28
28
|
end
|
29
|
-
|
30
|
-
conn
|
31
29
|
end
|
32
30
|
|
33
31
|
def self.get_conn_with_auth(verbose, url, user, password)
|
@@ -37,13 +35,11 @@ class Http
|
|
37
35
|
|
38
36
|
url = "https://#{url}" unless url?(url)
|
39
37
|
|
40
|
-
|
38
|
+
Faraday.new(url: url, ssl: { verify: false }) do |faraday|
|
41
39
|
faraday.request :url_encoded
|
42
40
|
faraday.request :basic_auth, user, password
|
43
41
|
faraday.response :logger if verbose
|
44
42
|
faraday.adapter Faraday.default_adapter
|
45
43
|
end
|
46
|
-
|
47
|
-
conn
|
48
44
|
end
|
49
45
|
end
|
data/lib/vmfloaty/logger.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
class FloatyLogger < ::Logger
|
@@ -17,11 +19,25 @@ class FloatyLogger < ::Logger
|
|
17
19
|
FloatyLogger.logger.error msg
|
18
20
|
end
|
19
21
|
|
22
|
+
def self.setlevel=(level)
|
23
|
+
level = level.downcase
|
24
|
+
case level
|
25
|
+
when 'debug'
|
26
|
+
logger.level = ::Logger::DEBUG
|
27
|
+
when 'info'
|
28
|
+
logger.level = ::Logger::INFO
|
29
|
+
when 'error'
|
30
|
+
logger.level = ::Logger::ERROR
|
31
|
+
else
|
32
|
+
error('set loglevel to debug, info or error')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
20
36
|
def initialize
|
21
|
-
super(
|
37
|
+
super($stderr)
|
22
38
|
self.level = ::Logger::INFO
|
23
|
-
self.formatter = proc do |
|
24
|
-
|
39
|
+
self.formatter = proc do |_severity, _datetime, _progname, msg|
|
40
|
+
"#{msg}\n"
|
25
41
|
end
|
26
42
|
end
|
27
43
|
end
|
@@ -22,7 +22,7 @@ class NonstandardPooler
|
|
22
22
|
status['reserved_hosts'] || []
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.retrieve(verbose, os_type, token, url, _user, _options,
|
25
|
+
def self.retrieve(verbose, os_type, token, url, _user, _options, _ondemand = nil, _continue = nil)
|
26
26
|
conn = Http.get_conn(verbose, url)
|
27
27
|
conn.headers['X-AUTH-TOKEN'] = token if token
|
28
28
|
|
@@ -46,7 +46,8 @@ class NonstandardPooler
|
|
46
46
|
raise TokenError, 'Token provided was nil; Request cannot be made to modify VM' if token.nil?
|
47
47
|
|
48
48
|
modify_hash.each do |key, _value|
|
49
|
-
raise ModifyError, "Configured service type does not support modification of #{key}" unless %i[reason
|
49
|
+
raise ModifyError, "Configured service type does not support modification of #{key}" unless %i[reason
|
50
|
+
reserved_for_reason].include? key
|
50
51
|
end
|
51
52
|
|
52
53
|
if modify_hash[:reason]
|
data/lib/vmfloaty/pooler.rb
CHANGED
@@ -12,13 +12,11 @@ class Pooler
|
|
12
12
|
response = conn.get 'vm'
|
13
13
|
response_body = JSON.parse(response.body)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
hosts
|
15
|
+
if os_filter
|
16
|
+
response_body.select { |i| i[/#{os_filter}/] }
|
17
|
+
else
|
18
|
+
response_body
|
19
|
+
end
|
22
20
|
end
|
23
21
|
|
24
22
|
def self.list_active(verbose, url, token, _user)
|
@@ -28,7 +26,7 @@ class Pooler
|
|
28
26
|
vms
|
29
27
|
end
|
30
28
|
|
31
|
-
def self.retrieve(verbose, os_type, token, url, _user, _options, ondemand = nil)
|
29
|
+
def self.retrieve(verbose, os_type, token, url, _user, _options, ondemand = nil, _continue = nil)
|
32
30
|
# NOTE:
|
33
31
|
# Developers can use `Utils.generate_os_hash` to
|
34
32
|
# generate the os_type param.
|
@@ -50,7 +48,10 @@ class Pooler
|
|
50
48
|
elsif response.status == 403
|
51
49
|
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/vm/#{os_string}. Request exceeds the configured per pool maximum. #{res_body}"
|
52
50
|
else
|
53
|
-
|
51
|
+
unless ondemand
|
52
|
+
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/vm/#{os_string}. #{res_body}"
|
53
|
+
end
|
54
|
+
|
54
55
|
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/ondemandvm/#{os_string}. #{res_body}"
|
55
56
|
end
|
56
57
|
end
|
@@ -63,7 +64,7 @@ class Pooler
|
|
63
64
|
FloatyLogger.info "waiting for request #{request_id} to be fulfilled"
|
64
65
|
sleep 5
|
65
66
|
end
|
66
|
-
FloatyLogger.info
|
67
|
+
FloatyLogger.info 'The request has been fulfilled'
|
67
68
|
check_ondemandvm(verbose, request_id, url)
|
68
69
|
end
|
69
70
|
|
@@ -84,8 +85,9 @@ class Pooler
|
|
84
85
|
def self.modify(verbose, url, hostname, token, modify_hash)
|
85
86
|
raise TokenError, 'Token provided was nil. Request cannot be made to modify vm' if token.nil?
|
86
87
|
|
87
|
-
modify_hash.
|
88
|
-
raise ModifyError, "Configured service type does not support modification of #{key}." unless %i[tags lifetime
|
88
|
+
modify_hash.each_key do |key|
|
89
|
+
raise ModifyError, "Configured service type does not support modification of #{key}." unless %i[tags lifetime
|
90
|
+
disk].include? key
|
89
91
|
end
|
90
92
|
|
91
93
|
conn = Http.get_conn(verbose, url)
|
@@ -120,8 +122,7 @@ class Pooler
|
|
120
122
|
|
121
123
|
response = conn.post "vm/#{hostname}/disk/#{disk}"
|
122
124
|
|
123
|
-
|
124
|
-
res_body
|
125
|
+
JSON.parse(response.body)
|
125
126
|
end
|
126
127
|
|
127
128
|
def self.delete(verbose, url, hosts, token, _user)
|
@@ -146,25 +147,21 @@ class Pooler
|
|
146
147
|
conn = Http.get_conn(verbose, url)
|
147
148
|
|
148
149
|
response = conn.get '/status'
|
149
|
-
|
150
|
-
res_body
|
150
|
+
JSON.parse(response.body)
|
151
151
|
end
|
152
152
|
|
153
153
|
def self.summary(verbose, url)
|
154
154
|
conn = Http.get_conn(verbose, url)
|
155
155
|
|
156
156
|
response = conn.get '/summary'
|
157
|
-
|
158
|
-
res_body
|
157
|
+
JSON.parse(response.body)
|
159
158
|
end
|
160
159
|
|
161
160
|
def self.query(verbose, url, hostname)
|
162
161
|
conn = Http.get_conn(verbose, url)
|
163
162
|
|
164
163
|
response = conn.get "vm/#{hostname}"
|
165
|
-
|
166
|
-
|
167
|
-
res_body
|
164
|
+
JSON.parse(response.body)
|
168
165
|
end
|
169
166
|
|
170
167
|
def self.snapshot(verbose, url, hostname, token)
|
@@ -174,8 +171,7 @@ class Pooler
|
|
174
171
|
conn.headers['X-AUTH-TOKEN'] = token
|
175
172
|
|
176
173
|
response = conn.post "vm/#{hostname}/snapshot"
|
177
|
-
|
178
|
-
res_body
|
174
|
+
JSON.parse(response.body)
|
179
175
|
end
|
180
176
|
|
181
177
|
def self.revert(verbose, url, hostname, token, snapshot_sha)
|
@@ -187,7 +183,6 @@ class Pooler
|
|
187
183
|
raise "Snapshot SHA provided was nil, could not revert #{hostname}" if snapshot_sha.nil?
|
188
184
|
|
189
185
|
response = conn.post "vm/#{hostname}/snapshot/#{snapshot_sha}"
|
190
|
-
|
191
|
-
res_body
|
186
|
+
JSON.parse(response.body)
|
192
187
|
end
|
193
188
|
end
|
data/lib/vmfloaty/service.rb
CHANGED
@@ -39,7 +39,7 @@ class Service
|
|
39
39
|
def user
|
40
40
|
unless @config['user']
|
41
41
|
FloatyLogger.info "Enter your #{@config['url']} service username:"
|
42
|
-
@config['user'] =
|
42
|
+
@config['user'] = $stdin.gets.chomp
|
43
43
|
end
|
44
44
|
@config['user']
|
45
45
|
end
|
@@ -77,10 +77,10 @@ class Service
|
|
77
77
|
@service_object.list_active verbose, url, token, user
|
78
78
|
end
|
79
79
|
|
80
|
-
def retrieve(verbose, os_types, use_token = true, ondemand = nil)
|
80
|
+
def retrieve(verbose, os_types, use_token = true, ondemand = nil, continue = nil)
|
81
81
|
FloatyLogger.info 'Requesting a vm without a token...' unless use_token
|
82
82
|
token_value = use_token ? token : nil
|
83
|
-
@service_object.retrieve verbose, os_types, token_value, url, user, @config, ondemand
|
83
|
+
@service_object.retrieve verbose, os_types, token_value, url, user, @config, ondemand, continue
|
84
84
|
end
|
85
85
|
|
86
86
|
def wait_for_request(verbose, requestid)
|
@@ -139,9 +139,9 @@ class Service
|
|
139
139
|
|
140
140
|
# some methods do not exist for ABS, and if possible should target the Pooler service
|
141
141
|
def maybe_use_vmpooler
|
142
|
-
if @service_object
|
143
|
-
|
144
|
-
FloatyLogger.info
|
142
|
+
if @service_object == ABS # this is not an instance
|
143
|
+
unless 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
145
|
self.silent = true
|
146
146
|
end
|
147
147
|
|
data/lib/vmfloaty/utils.rb
CHANGED
@@ -39,7 +39,10 @@ class Utils
|
|
39
39
|
# "engine"=>"vmpooler"
|
40
40
|
# }
|
41
41
|
|
42
|
-
|
42
|
+
unless response_body.delete('ok')
|
43
|
+
raise ArgumentError,
|
44
|
+
"Bad GET response passed to format_hosts: #{response_body.to_json}"
|
45
|
+
end
|
43
46
|
|
44
47
|
# vmpooler reports the domain separately from the hostname
|
45
48
|
domain = response_body.delete('domain')
|
@@ -50,7 +53,7 @@ class Utils
|
|
50
53
|
abs_job_id = response_body.delete('job_id')
|
51
54
|
result['job_id'] = abs_job_id unless abs_job_id.nil?
|
52
55
|
|
53
|
-
filtered_response_body = response_body.reject { |key, _|
|
56
|
+
filtered_response_body = response_body.reject { |key, _| %w[request_id ready].include?(key) }
|
54
57
|
filtered_response_body.each do |os, value|
|
55
58
|
hostnames = Array(value['hostname'])
|
56
59
|
hostnames.map! { |host| "#{host}.#{domain}" } if domain
|
@@ -106,7 +109,7 @@ class Utils
|
|
106
109
|
def self.pretty_print_hosts(verbose, service, hostnames = [], print_to_stderr = false, indent = 0)
|
107
110
|
output_target = print_to_stderr ? $stderr : $stdout
|
108
111
|
|
109
|
-
fetched_data =
|
112
|
+
fetched_data = get_host_data(verbose, service, hostnames)
|
110
113
|
fetched_data.each do |hostname, host_data|
|
111
114
|
case service.type
|
112
115
|
when 'ABS'
|
@@ -116,13 +119,14 @@ class Utils
|
|
116
119
|
|
117
120
|
output_target.puts "- [JobID:#{host_data['request']['job']['id']}] <#{host_data['state']}>"
|
118
121
|
host_data['allocated_resources'].each do |allocated_resources, _i|
|
119
|
-
if allocated_resources['engine'] ==
|
122
|
+
if (allocated_resources['engine'] == 'vmpooler' || allocated_resources['engine'] == 'ondemand') && service.config['vmpooler_fallback']
|
120
123
|
vmpooler_service = service.clone
|
121
124
|
vmpooler_service.silent = true
|
122
125
|
vmpooler_service.maybe_use_vmpooler
|
123
|
-
|
126
|
+
pretty_print_hosts(verbose, vmpooler_service, allocated_resources['hostname'].split('.')[0],
|
127
|
+
print_to_stderr, indent + 2)
|
124
128
|
else
|
125
|
-
#TODO we could add more specific metadata for the other services, nspooler and aws
|
129
|
+
# TODO: we could add more specific metadata for the other services, nspooler and aws
|
126
130
|
output_target.puts " - #{allocated_resources['hostname']} (#{allocated_resources['type']})"
|
127
131
|
end
|
128
132
|
end
|
@@ -130,8 +134,13 @@ class Utils
|
|
130
134
|
tag_pairs = []
|
131
135
|
tag_pairs = host_data['tags'].map { |key, value| "#{key}: #{value}" } unless host_data['tags'].nil?
|
132
136
|
duration = "#{host_data['running']}/#{host_data['lifetime']} hours"
|
133
|
-
metadata = [host_data['template'], duration, *tag_pairs]
|
134
|
-
|
137
|
+
metadata = [host_data['state'], host_data['template'], duration, *tag_pairs]
|
138
|
+
message = "- #{hostname}.#{host_data['domain']} (#{metadata.join(', ')})".gsub(/^/, ' ' * indent)
|
139
|
+
if host_data['state'] && host_data['state'] == 'destroyed'
|
140
|
+
output_target.puts message.colorize(:red)
|
141
|
+
else
|
142
|
+
output_target.puts message
|
143
|
+
end
|
135
144
|
when 'NonstandardPooler'
|
136
145
|
line = "- #{host_data['fqdn']} (#{host_data['os_triple']}"
|
137
146
|
line += ", #{host_data['hours_left_on_reservation']}h remaining"
|
@@ -148,30 +157,26 @@ class Utils
|
|
148
157
|
result = {}
|
149
158
|
hostnames = [hostnames] unless hostnames.is_a? Array
|
150
159
|
hostnames.each do |hostname|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
160
|
+
response = service.query(verbose, hostname)
|
161
|
+
host_data = response[hostname]
|
162
|
+
if block_given?
|
163
|
+
yield host_data result
|
164
|
+
else
|
165
|
+
case service.type
|
166
|
+
when 'ABS'
|
167
|
+
# For ABS, 'hostname' variable is the jobID
|
168
|
+
result[hostname] = host_data if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
|
169
|
+
when 'Pooler'
|
170
|
+
result[hostname] = host_data
|
171
|
+
when 'NonstandardPooler'
|
172
|
+
result[hostname] = host_data
|
156
173
|
else
|
157
|
-
|
158
|
-
when 'ABS'
|
159
|
-
# For ABS, 'hostname' variable is the jobID
|
160
|
-
if host_data['state'] == 'allocated' || host_data['state'] == 'filled'
|
161
|
-
result[hostname] = host_data
|
162
|
-
end
|
163
|
-
when 'Pooler'
|
164
|
-
result[hostname] = host_data
|
165
|
-
when 'NonstandardPooler'
|
166
|
-
result[hostname] = host_data
|
167
|
-
else
|
168
|
-
raise "Invalid service type #{service.type}"
|
169
|
-
end
|
174
|
+
raise "Invalid service type #{service.type}"
|
170
175
|
end
|
171
|
-
rescue StandardError => e
|
172
|
-
FloatyLogger.error("Something went wrong while trying to gather information on #{hostname}:")
|
173
|
-
FloatyLogger.error(e)
|
174
176
|
end
|
177
|
+
rescue StandardError => e
|
178
|
+
FloatyLogger.error("Something went wrong while trying to gather information on #{hostname}:")
|
179
|
+
FloatyLogger.error(e)
|
175
180
|
end
|
176
181
|
result
|
177
182
|
end
|
@@ -187,16 +192,14 @@ class Utils
|
|
187
192
|
|
188
193
|
width = pools.keys.map(&:length).max
|
189
194
|
pools.each do |name, pool|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
FloatyLogger.error "#{name.ljust(width)} #{e.red}"
|
199
|
-
end
|
195
|
+
max = pool['max']
|
196
|
+
ready = pool['ready']
|
197
|
+
pending = pool['pending']
|
198
|
+
missing = max - ready - pending
|
199
|
+
char = 'o'
|
200
|
+
puts "#{name.ljust(width)} #{(char * ready).green}#{(char * pending).yellow}#{(char * missing).red}"
|
201
|
+
rescue StandardError => e
|
202
|
+
FloatyLogger.error "#{name.ljust(width)} #{e.red}"
|
200
203
|
end
|
201
204
|
puts message.colorize(status_response['status']['ok'] ? :default : :red)
|
202
205
|
when 'NonstandardPooler'
|
@@ -206,16 +209,14 @@ class Utils
|
|
206
209
|
|
207
210
|
width = pools.keys.map(&:length).max
|
208
211
|
pools.each do |name, pool|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
FloatyLogger.error "#{name.ljust(width)} #{e.red}"
|
218
|
-
end
|
212
|
+
max = pool['total_hosts']
|
213
|
+
ready = pool['available_hosts']
|
214
|
+
pending = pool['pending'] || 0 # not available for nspooler
|
215
|
+
missing = max - ready - pending
|
216
|
+
char = 'o'
|
217
|
+
puts "#{name.ljust(width)} #{(char * ready).green}#{(char * pending).yellow}#{(char * missing).red}"
|
218
|
+
rescue StandardError => e
|
219
|
+
FloatyLogger.error "#{name.ljust(width)} #{e.red}"
|
219
220
|
end
|
220
221
|
when 'ABS'
|
221
222
|
FloatyLogger.error 'ABS Not OK' unless status_response
|
@@ -251,10 +252,11 @@ class Utils
|
|
251
252
|
def self.get_service_config(config, options)
|
252
253
|
# The top-level url, user, and token values in the config file are treated as defaults
|
253
254
|
service_config = {
|
254
|
-
'url'
|
255
|
-
'user'
|
255
|
+
'url' => config['url'],
|
256
|
+
'user' => config['user'],
|
256
257
|
'token' => config['token'],
|
257
|
-
'
|
258
|
+
'vmpooler_fallback' => config['vmpooler_fallback'],
|
259
|
+
'type' => config['type'] || 'vmpooler'
|
258
260
|
}
|
259
261
|
|
260
262
|
if config['services']
|
@@ -265,7 +267,10 @@ class Utils
|
|
265
267
|
service_config.merge! values
|
266
268
|
else
|
267
269
|
# If the user provided a service name at the command line, use that service if posible, or fail
|
268
|
-
|
270
|
+
unless config['services'][options.service]
|
271
|
+
raise ArgumentError,
|
272
|
+
"Could not find a configured service named '#{options.service}' in ~/.vmfloaty.yml"
|
273
|
+
end
|
269
274
|
|
270
275
|
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
271
276
|
service_config.merge! config['services'][options.service]
|
@@ -289,22 +294,22 @@ class Utils
|
|
289
294
|
config = Conf.read_config
|
290
295
|
# The top-level url, user, and token values in the config file are treated as defaults
|
291
296
|
service_config = {
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
297
|
+
'url' => config['url'],
|
298
|
+
'user' => config['user'],
|
299
|
+
'token' => config['token'],
|
300
|
+
'type' => 'vmpooler'
|
296
301
|
}
|
297
302
|
|
298
303
|
# at a minimum, the url needs to be configured
|
299
304
|
if config['services'] && config['services'][vmpooler_fallback] && config['services'][vmpooler_fallback]['url']
|
300
305
|
# If the service is configured but some values are missing, use the top-level defaults to fill them in
|
301
306
|
service_config.merge! config['services'][vmpooler_fallback]
|
307
|
+
elsif vmpooler_fallback.nil?
|
308
|
+
raise ArgumentError,
|
309
|
+
"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'"
|
302
310
|
else
|
303
|
-
|
304
|
-
|
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
|
311
|
+
raise ArgumentError,
|
312
|
+
"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'"
|
308
313
|
end
|
309
314
|
|
310
315
|
service_config
|