floatyhelper 2.0.5 → 2.1.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/lib/floatyhelper/floaty.rb +15 -2
- data/lib/floatyhelper/version.rb +1 -1
- data/lib/floatyhelper/vm.rb +91 -15
- data/lib/floatyhelper.rb +110 -4
- metadata +3 -3
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 = {
|
@@ -12,7 +13,7 @@ class Floaty
|
|
12
13
|
},
|
13
14
|
'vmpooler' => {
|
14
15
|
'type' => 'vm',
|
15
|
-
'url' => '
|
16
|
+
'url' => 'https://vmpooler-prod.k8s.infracore.puppet.net',
|
16
17
|
},
|
17
18
|
'nspooler' => {
|
18
19
|
'type' => 'nonstandard',
|
@@ -32,6 +33,12 @@ class Floaty
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
def self.vmpooler_url
|
37
|
+
data = load_vmfloaty_config
|
38
|
+
url = data['services'] && data['services']['vmpooler'] ? data['services']['vmpooler']['url'] : nil
|
39
|
+
url || FLOATY_SERVICES['vmpooler']['url']
|
40
|
+
end
|
41
|
+
|
35
42
|
# This adds some stdout printing here which I was trying to avoid, but
|
36
43
|
# I think it makes sense to put it here rather than the main floatyhelper
|
37
44
|
# somewhere.
|
@@ -131,10 +138,16 @@ class Floaty
|
|
131
138
|
while !Process.waitpid(io.pid, Process::WNOHANG)
|
132
139
|
line = io.gets
|
133
140
|
unless line.nil?
|
134
|
-
|
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
|
135
143
|
output += line
|
136
144
|
end
|
137
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
|
138
151
|
end
|
139
152
|
# Need to check exit status
|
140
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,26 +19,30 @@ 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
|
32
|
+
vmpooler_url = Floaty.vmpooler_url
|
27
33
|
begin # rubocop:disable Style/RedundantBegin
|
28
|
-
uri = URI.parse(
|
34
|
+
uri = URI.parse("#{vmpooler_url}/status")
|
29
35
|
http = Net::HTTP.new(uri.host, uri.port)
|
30
36
|
http.use_ssl = true
|
31
37
|
result = http.get(uri.request_uri)
|
32
38
|
result = JSON.parse(result.body)
|
33
39
|
# Techinally, 'max > 0' tells you if it's a pooled platform, but if
|
34
40
|
# the pool is empty, we'll want to fall back to ABS anyway.
|
35
|
-
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
|
36
42
|
rescue StandardError
|
37
43
|
# Not a great practice to swallow all errors, but this list is probably
|
38
44
|
# pretty stable, so let's just pass along the default.
|
39
|
-
puts
|
45
|
+
puts "Error looking up pooled platforms from #{vmpooler_url}/status. Using default pooled platform list instead.".yellow
|
40
46
|
pooled_platforms_default
|
41
47
|
end
|
42
48
|
end
|
@@ -47,7 +53,7 @@ class VM
|
|
47
53
|
Groups.delete_tag(id) if Groups.tag?(id)
|
48
54
|
Groups.delete_all if id == 'all'
|
49
55
|
hosts = hosts.select { |host| alive(host) }
|
50
|
-
puts Floaty.floaty_cmd("delete #{
|
56
|
+
hosts.each_slice(10) { |s| puts Floaty.floaty_cmd("delete #{s.join(',')} --service vmpooler") } unless hosts.empty?
|
51
57
|
end
|
52
58
|
|
53
59
|
def self.query(host)
|
@@ -148,28 +154,98 @@ class VM
|
|
148
154
|
end
|
149
155
|
|
150
156
|
def self.revert(id, snaptag)
|
157
|
+
clr = Config.get_config_setting('vertical_snapshot_status')
|
158
|
+
clr = clr.to_s.downcase == 'true'
|
151
159
|
snaptag ||= 'Blank Snaptag'
|
152
160
|
shas = VM.getsnapshot(id, snaptag)
|
161
|
+
lastlog = {}
|
162
|
+
puts `tput clear` if clr
|
153
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",'')
|
154
166
|
print "#{host}: #{sha} --- "
|
155
167
|
puts Floaty.floaty_cmd("revert #{host} #{sha} --service vmpooler")
|
156
168
|
end
|
157
|
-
puts
|
158
|
-
|
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
|
159
203
|
end
|
160
204
|
|
161
205
|
### Get a VM from floaty ###
|
162
|
-
def self.get_vm(platform: 'centos-7-x86_64', force_abs: false)
|
163
|
-
|
164
|
-
|
165
|
-
|
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) }
|
166
242
|
# When use_pty is true, we've already printed stderr/stdout, so no need to do so again.
|
167
|
-
raise "Error obtaining a VM" if
|
168
|
-
|
243
|
+
raise "Error obtaining a VM" if vms.empty?
|
244
|
+
vms
|
169
245
|
else
|
170
|
-
response = Floaty.floaty_cmd("get #{platform} --service vmpooler")
|
246
|
+
response = Floaty.floaty_cmd("get #{platform}=#{number} --service vmpooler")
|
171
247
|
raise "Error obtaining a VM: #{response}" if response.include?('error')
|
172
|
-
response
|
248
|
+
parse_response(response)
|
173
249
|
end
|
174
250
|
end
|
175
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
|
@@ -203,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
203
|
- !ruby/object:Gem::Version
|
204
204
|
version: '0'
|
205
205
|
requirements: []
|
206
|
-
rubygems_version: 3.1.
|
206
|
+
rubygems_version: 3.1.6
|
207
207
|
signing_key:
|
208
208
|
specification_version: 4
|
209
209
|
summary: CLI tool for manipulating Puppet's vmpooler VMs with Vmfloaty.
|