floatyhelper 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/floatyhelper/floaty.rb +8 -1
- data/lib/floatyhelper/version.rb +1 -1
- data/lib/floatyhelper/vm.rb +88 -13
- data/lib/floatyhelper.rb +110 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0a23b077fb6f7d707267d06af856f7eb2c5854bd1ae65b325fd8098a57c5623
|
4
|
+
data.tar.gz: 812e17bf9526dc862df4095119ee388d6e4e8113773a7311600fe6e35ccde743
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70b52c8b29fcb0dd530bd39efd69961da64784383705c57fc315e6c7bd013f9acc5339c46d66aeca3bdcb549bdc3b11f0b40e7ed0a987e93867ebd596c6afcb2
|
7
|
+
data.tar.gz: fe9a5d8fae49c366df1c6a125c1600d9d11b6c9c759bcc25e4aec7f2e9fce3a6a28785a61e42c9fae79efc943307694f0e71b39114b2e796e71637749040e8b8
|
data/lib/floatyhelper/floaty.rb
CHANGED
@@ -3,6 +3,7 @@ require 'json'
|
|
3
3
|
require 'open3'
|
4
4
|
require 'colorize'
|
5
5
|
require 'pty'
|
6
|
+
require 'thread'
|
6
7
|
|
7
8
|
class Floaty
|
8
9
|
FLOATY_SERVICES = {
|
@@ -137,10 +138,16 @@ class Floaty
|
|
137
138
|
while !Process.waitpid(io.pid, Process::WNOHANG)
|
138
139
|
line = io.gets
|
139
140
|
unless line.nil?
|
140
|
-
|
141
|
+
# Always print initial job_id request, VM results, or if we aren't running in a separate thread.
|
142
|
+
puts line if line =~ /^\s*-\s*[\w-]+\./ || line =~ /Requesting VMs/ || Thread.current['print'].nil? || Thread.current['print'] == true
|
141
143
|
output += line
|
142
144
|
end
|
143
145
|
end
|
146
|
+
# If the process finishes before we get all the output, get the rest and handle it.
|
147
|
+
while line = io.gets
|
148
|
+
puts line if line =~ /^\s*-\s*[\w-]+\./ || Thread.current['print'].nil? || Thread.current['print'] == true
|
149
|
+
output += line
|
150
|
+
end
|
144
151
|
end
|
145
152
|
# Need to check exit status
|
146
153
|
output
|
data/lib/floatyhelper/version.rb
CHANGED
data/lib/floatyhelper/vm.rb
CHANGED
@@ -7,8 +7,10 @@ require 'floatyhelper/config'
|
|
7
7
|
require 'colorize'
|
8
8
|
require 'net/http'
|
9
9
|
require 'json'
|
10
|
+
require 'thread'
|
10
11
|
|
11
12
|
class VM
|
13
|
+
# Assume we only have 1 of each if we can't find the info
|
12
14
|
def self.pooled_platforms_default
|
13
15
|
[
|
14
16
|
'centos-7-x86_64',
|
@@ -17,10 +19,13 @@ class VM
|
|
17
19
|
'redhat-7-x86_64',
|
18
20
|
'redhat-8-x86_64',
|
19
21
|
'redhat-fips-7-x86_64',
|
22
|
+
'redhat-fips-8-x86_64',
|
20
23
|
'scientific-7-x86_64',
|
21
24
|
'sles-12-x86_64',
|
25
|
+
'sles-15-x86_64',
|
22
26
|
'ubuntu-1804-x86_64',
|
23
|
-
|
27
|
+
'ubuntu-2004-x86_64',
|
28
|
+
].map { |x| [x,1] }.to_h
|
24
29
|
end
|
25
30
|
|
26
31
|
def self.find_pooled_platforms
|
@@ -33,7 +38,7 @@ class VM
|
|
33
38
|
result = JSON.parse(result.body)
|
34
39
|
# Techinally, 'max > 0' tells you if it's a pooled platform, but if
|
35
40
|
# the pool is empty, we'll want to fall back to ABS anyway.
|
36
|
-
result['pools'].select { |_pool, info| info['ready'].positive? }.map { |pool,
|
41
|
+
result['pools'].select { |_pool, info| info['ready'].positive? }.map { |pool, info| [pool.gsub('-pixa4','').gsub('-pooled',''), info['ready']] }.to_h
|
37
42
|
rescue StandardError
|
38
43
|
# Not a great practice to swallow all errors, but this list is probably
|
39
44
|
# pretty stable, so let's just pass along the default.
|
@@ -48,7 +53,7 @@ class VM
|
|
48
53
|
Groups.delete_tag(id) if Groups.tag?(id)
|
49
54
|
Groups.delete_all if id == 'all'
|
50
55
|
hosts = hosts.select { |host| alive(host) }
|
51
|
-
puts Floaty.floaty_cmd("delete #{
|
56
|
+
hosts.each_slice(10) { |s| puts Floaty.floaty_cmd("delete #{s.join(',')} --service vmpooler") } unless hosts.empty?
|
52
57
|
end
|
53
58
|
|
54
59
|
def self.query(host)
|
@@ -149,28 +154,98 @@ class VM
|
|
149
154
|
end
|
150
155
|
|
151
156
|
def self.revert(id, snaptag)
|
157
|
+
clr = Config.get_config_setting('vertical_snapshot_status')
|
158
|
+
clr = clr.to_s.downcase == 'true'
|
152
159
|
snaptag ||= 'Blank Snaptag'
|
153
160
|
shas = VM.getsnapshot(id, snaptag)
|
161
|
+
lastlog = {}
|
162
|
+
puts `tput clear` if clr
|
154
163
|
shas.each do |host, sha|
|
164
|
+
# Force a session to show up in lastlog
|
165
|
+
lastlog[host] = `ssh -t root@#{host} lastlog -u root 2>/dev/null`.gsub("\r",'').gsub("\n",'')
|
155
166
|
print "#{host}: #{sha} --- "
|
156
167
|
puts Floaty.floaty_cmd("revert #{host} #{sha} --service vmpooler")
|
157
168
|
end
|
158
|
-
puts
|
159
|
-
|
169
|
+
puts
|
170
|
+
puts 'Waiting for all VMs to finish reverting...'
|
171
|
+
alldone = false
|
172
|
+
until alldone
|
173
|
+
puts `tput cup #{shas.count + 2}` if clr
|
174
|
+
alldone = true
|
175
|
+
print "\r" unless clr
|
176
|
+
lastlog.each do |host, value|
|
177
|
+
begin
|
178
|
+
result =`ssh root@#{host} lastlog -u root 2>/dev/null`.gsub("\r",'').gsub("\n",'')
|
179
|
+
# Original will have some garbage about pseudo-terminal that we won't have here
|
180
|
+
# Also, can't do this on Windows yet, so pretend we're done for those hosts.
|
181
|
+
done = !lastlog[host].include?(result) || result.empty?
|
182
|
+
rescue
|
183
|
+
done = false
|
184
|
+
end
|
185
|
+
status = done ? 'Done'.green : 'Wait'.yellow
|
186
|
+
if clr
|
187
|
+
puts "* %s #{status} *" % "#{host}:".ljust(16)
|
188
|
+
else
|
189
|
+
print "* %s #{status} *" % "#{host}:".ljust(16)
|
190
|
+
end
|
191
|
+
alldone &= done
|
192
|
+
end
|
193
|
+
sleep(1)
|
194
|
+
end
|
195
|
+
puts ''
|
196
|
+
end
|
197
|
+
|
198
|
+
# Response should be something like:
|
199
|
+
# - vm1.delivery.puppetlabs.net (platform-tag)\n
|
200
|
+
# - vm2.delivery.puppetlabs.net (platform-tag)\n
|
201
|
+
def self.parse_response(response)
|
202
|
+
response.scan(/- ([\w-]+)(?:.vmpooler-prod.puppet.net|.delivery.puppetlabs.net) \(/).flatten
|
160
203
|
end
|
161
204
|
|
162
205
|
### Get a VM from floaty ###
|
163
|
-
def self.get_vm(platform: 'centos-7-x86_64', force_abs: false)
|
164
|
-
|
165
|
-
|
166
|
-
|
206
|
+
def self.get_vm(platform: 'centos-7-x86_64', force_abs: false, number: 1)
|
207
|
+
pools = find_pooled_platforms
|
208
|
+
platform = platform.gsub('-pixa4','')
|
209
|
+
vms_per_thread = 10
|
210
|
+
if !pools.keys.include?(platform) || force_abs || pools[platform] < number
|
211
|
+
numthreads = number / vms_per_thread
|
212
|
+
numthreads += 1 if number % vms_per_thread != 0
|
213
|
+
threads = []
|
214
|
+
responses = []
|
215
|
+
mutex = Mutex.new
|
216
|
+
numleft = number
|
217
|
+
numthreads.times do
|
218
|
+
num = numleft < vms_per_thread ? numleft : vms_per_thread
|
219
|
+
numleft -= num
|
220
|
+
threads << Thread.new do
|
221
|
+
Thread.current['print'] = false
|
222
|
+
myresponse = Floaty.floaty_cmd("get #{platform}=#{num} --service abs --priority 1 --force", use_pty: true)
|
223
|
+
mutex.synchronize { responses << myresponse }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
# Setting print to true then joining causes the thread not
|
227
|
+
# to pick up the variable change sometimes. So this polls
|
228
|
+
# the thread status and sets the print variable in a loop.
|
229
|
+
puts "Fetching VMs using #{threads.count} thread#{threads.count > 1 ? 's' : ''}. Output shown will be from one of the currently alive threads, as well as the result of any completed threads, until all threads are finished.".cyan unless threads.count == 1
|
230
|
+
while threads.any? { |t| t.alive? } do
|
231
|
+
# If we have no living threads with print set to true,
|
232
|
+
# pick one and turn it on.
|
233
|
+
if threads.select { |t| t.alive? && t['print'] == true }.empty?
|
234
|
+
t = threads.find{ |t| t.alive? }
|
235
|
+
t['print'] = true if !t.nil?
|
236
|
+
end
|
237
|
+
end
|
238
|
+
# Just in case
|
239
|
+
threads.each { |t| t.join }
|
240
|
+
vms = []
|
241
|
+
responses.each { |r| vms += parse_response(r) }
|
167
242
|
# When use_pty is true, we've already printed stderr/stdout, so no need to do so again.
|
168
|
-
raise "Error obtaining a VM" if
|
169
|
-
|
243
|
+
raise "Error obtaining a VM" if vms.empty?
|
244
|
+
vms
|
170
245
|
else
|
171
|
-
response = Floaty.floaty_cmd("get #{platform} --service vmpooler")
|
246
|
+
response = Floaty.floaty_cmd("get #{platform}=#{number} --service vmpooler")
|
172
247
|
raise "Error obtaining a VM: #{response}" if response.include?('error')
|
173
|
-
response
|
248
|
+
parse_response(response)
|
174
249
|
end
|
175
250
|
end
|
176
251
|
end
|
data/lib/floatyhelper.rb
CHANGED
@@ -10,6 +10,8 @@ require 'floatyhelper/groups'
|
|
10
10
|
require 'floatyhelper/vm'
|
11
11
|
require 'floatyhelper/version'
|
12
12
|
require 'colorize'
|
13
|
+
require 'open3'
|
14
|
+
require 'thread'
|
13
15
|
|
14
16
|
class Floatyhelper
|
15
17
|
include Commander::Methods
|
@@ -371,15 +373,24 @@ class Floatyhelper
|
|
371
373
|
ABS with the --abs flag.
|
372
374
|
EOT
|
373
375
|
c.option '--abs', 'Force use of ABS to get platform, even if platform is pooled.'
|
376
|
+
c.option '--number INTEGER', Integer, 'Number of VMs of this flavor to check out. Default 1.'
|
377
|
+
c.option '-y', "Don't ask if we're sure we want to request more than 10 VMs."
|
374
378
|
c.action do |args, options|
|
375
379
|
platform = args[0] || 'centos-7-x86_64'
|
376
380
|
tag = args.length > 1 ? args[1] : 'Unassigned'
|
381
|
+
num = options.number || 1
|
382
|
+
if num > 10 && !options.y
|
383
|
+
answer = ask("Checking out more than 10 VMs at once can have an impact on the Vmpooler infrastructure. Please do not use this option unless you know what you're doing and clean them up when you're finished. Proceed? [Y/n]".yellow)
|
384
|
+
if !answer.empty? && answer.capitalize != 'Y'
|
385
|
+
return
|
386
|
+
end
|
387
|
+
end
|
377
388
|
begin
|
378
|
-
|
379
|
-
Groups.addhosts(
|
380
|
-
puts "Added #{
|
389
|
+
hosts = VM.get_vm(platform: platform, force_abs: options.abs, number: num)
|
390
|
+
Groups.addhosts(hosts, tag)
|
391
|
+
puts "Added #{hosts.join(', ')} to tag #{tag}".green
|
381
392
|
amount = Config.get_config_setting('increaselife').to_i
|
382
|
-
VM.increaselife(
|
393
|
+
VM.increaselife(hosts, amount)
|
383
394
|
rescue StandardError => e
|
384
395
|
puts e.message.red
|
385
396
|
end
|
@@ -395,6 +406,101 @@ class Floatyhelper
|
|
395
406
|
end
|
396
407
|
end
|
397
408
|
|
409
|
+
command :runon do |c|
|
410
|
+
c.syntax = 'floatyhelper runon <tag> <command>'
|
411
|
+
c.summary = 'Run a command on all hosts under the given tag'
|
412
|
+
c.description = 'Run a command on all hosts under the given tag using SSH. Requires that the key to SSH into the hosts is in ssh-agent already.'
|
413
|
+
c.option '--user', 'Run command as a user other than root'
|
414
|
+
c.action do |args, options|
|
415
|
+
if args.length == 0
|
416
|
+
puts 'Must specify a tag and command to run'.red
|
417
|
+
return
|
418
|
+
end
|
419
|
+
if args.length == 1
|
420
|
+
puts 'Must specify a command to run'.red
|
421
|
+
return
|
422
|
+
end
|
423
|
+
|
424
|
+
tag = args[0]
|
425
|
+
command = args[1..].join(' ')
|
426
|
+
user = options.user || 'root'
|
427
|
+
|
428
|
+
if !Groups.tag?(tag)
|
429
|
+
puts "#{tag} does not appear to be a tag managed by floatyhelper.".red
|
430
|
+
return
|
431
|
+
end
|
432
|
+
hosts = Groups.get_hosts(tag)
|
433
|
+
threads = []
|
434
|
+
responses = []
|
435
|
+
mutex = Mutex.new
|
436
|
+
hosts.each do |host|
|
437
|
+
threads << Thread.new do
|
438
|
+
output, status = Open3.capture2e("ssh -A -o StrictHostKeyChecking=no #{user}@#{host}.delivery.puppetlabs.net \"#{command}\"")
|
439
|
+
mutex.synchronize do
|
440
|
+
responses << [host,output,status]
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
puts
|
445
|
+
(1..threads.count).each do |i|
|
446
|
+
print "\rWaiting for thread #{i}/#{threads.count} to finish..."
|
447
|
+
threads[i-1].join
|
448
|
+
end
|
449
|
+
print "\rDone\n"
|
450
|
+
responses.each do |r|
|
451
|
+
color = r[2].exitstatus.zero? ? :white : :red
|
452
|
+
puts "*** #{r[0]}: Exit code #{r[2].exitstatus} ***".colorize(color)
|
453
|
+
puts r[1].colorize(color)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
command :scpto do |c|
|
459
|
+
c.syntax = 'floatyhelper scp <tag> <path_to_local_file> <path_to_remote_file>'
|
460
|
+
c.summary = 'SCP a file from this host to all hosts under the given tag at the given path'
|
461
|
+
c.description = 'SCP a file from this host to all hosts under the given tag at the given path. This assumes the necessary SSH key is already loaded into ssh-agent.'
|
462
|
+
c.option '--user', 'Log in as a user other than root'
|
463
|
+
c.action do |args, options|
|
464
|
+
if args.length < 3
|
465
|
+
puts 'Must specify a tag, local path, and remote path'.red
|
466
|
+
return
|
467
|
+
end
|
468
|
+
|
469
|
+
tag = args[0]
|
470
|
+
local = args[1]
|
471
|
+
remote = args[2]
|
472
|
+
user = options.user || 'root'
|
473
|
+
|
474
|
+
if !Groups.tag?(tag)
|
475
|
+
puts "#{tag} does not appear to be a tag managed by floatyhelper.".red
|
476
|
+
return
|
477
|
+
end
|
478
|
+
hosts = Groups.get_hosts(tag)
|
479
|
+
threads = []
|
480
|
+
responses = []
|
481
|
+
mutex = Mutex.new
|
482
|
+
hosts.each do |host|
|
483
|
+
threads << Thread.new do
|
484
|
+
output, status = Open3.capture2e("scp #{local} #{user}@#{host}.delivery.puppetlabs.net:#{remote}")
|
485
|
+
mutex.synchronize do
|
486
|
+
responses << [host,output,status]
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
puts
|
491
|
+
(1..threads.count).each do |i|
|
492
|
+
print "\rWaiting for thread #{i}/#{threads.count} to finish..."
|
493
|
+
threads[i-1].join
|
494
|
+
end
|
495
|
+
print "\rDone\n"
|
496
|
+
responses.each do |r|
|
497
|
+
color = r[2].exitstatus.zero? ? :white : :red
|
498
|
+
puts "*** #{r[0]}: Exit code #{r[2].exitstatus} ***".colorize(color)
|
499
|
+
puts r[1].colorize(color)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
398
504
|
run!
|
399
505
|
end
|
400
506
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floatyhelper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Burgan-Illig
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|