rvc 1.7.0 → 1.8.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.
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/rvc/completion.rb +1 -1
- data/lib/rvc/extensions/ClusterComputeResource.rb +11 -0
- data/lib/rvc/extensions/ComputeResource.rb +5 -1
- data/lib/rvc/extensions/Datacenter.rb +1 -1
- data/lib/rvc/extensions/HostSystem.rb +15 -0
- data/lib/rvc/extensions/VirtualMachine.rb +7 -2
- data/lib/rvc/fs.rb +2 -2
- data/lib/rvc/inventory.rb +9 -1
- data/lib/rvc/modules/basic.rb +4 -1
- data/lib/rvc/modules/cluster.rb +99 -38
- data/lib/rvc/modules/connection.rb +2 -0
- data/lib/rvc/modules/device.rb +37 -1
- data/lib/rvc/modules/diagnostics.rb +119 -11
- data/lib/rvc/modules/find.rb +1 -1
- data/lib/rvc/modules/host.rb +116 -4
- data/lib/rvc/modules/perf.rb +53 -7
- data/lib/rvc/modules/snapshot.rb +7 -1
- data/lib/rvc/modules/spbm.rb +728 -0
- data/lib/rvc/modules/syslog.rb +103 -0
- data/lib/rvc/modules/vds.rb +59 -2
- data/lib/rvc/modules/vim.rb +1 -1
- data/lib/rvc/modules/vm.rb +70 -7
- data/lib/rvc/modules/vm_guest.rb +190 -91
- data/lib/rvc/modules/vnc.rb +29 -5
- data/lib/rvc/modules/vsan.rb +4105 -0
- data/lib/rvc/util.rb +31 -11
- metadata +7 -3
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright (c) 2013 VMware, Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rvc/vim'
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'net/ssh'
|
25
|
+
rescue LoadError
|
26
|
+
end
|
27
|
+
|
28
|
+
opts :config_syslog do
|
29
|
+
summary "Configure Syslog"
|
30
|
+
arg :entity, nil, :lookup => [VIM, VIM::HostSystem, VIM::ComputeResource, VIM::ClusterComputeResource]
|
31
|
+
arg :ip, nil, :type => :string
|
32
|
+
opt :vc_root_pwd, "VC root password for SSH access", :default => "vmware"
|
33
|
+
end
|
34
|
+
|
35
|
+
def config_syslog entity, ip, opts
|
36
|
+
if entity.is_a?(VIM)
|
37
|
+
puts "#{Time.now}: Finding all Hosts inside VC"
|
38
|
+
$shell.fs.marks['vcrvc'] = entity
|
39
|
+
hosts = []
|
40
|
+
hosts += $shell.fs.lookup("~vcrvc/*/computers/*/host")
|
41
|
+
hosts += $shell.fs.lookup("~vcrvc/*/computers/*/hosts/*")
|
42
|
+
elsif entity.is_a?(VIM::ComputeResource)
|
43
|
+
hosts = entity.host
|
44
|
+
else
|
45
|
+
hosts = [entity]
|
46
|
+
end
|
47
|
+
if hosts.length == 0
|
48
|
+
err "No hosts found"
|
49
|
+
end
|
50
|
+
conn = hosts.first._connection
|
51
|
+
pc = conn.propertyCollector
|
52
|
+
|
53
|
+
lock = Mutex.new
|
54
|
+
hosts_props = pc.collectMultiple(hosts,
|
55
|
+
'name',
|
56
|
+
'runtime.connectionState',
|
57
|
+
)
|
58
|
+
connected_hosts = hosts_props.select do |k,v|
|
59
|
+
v['runtime.connectionState'] == 'connected'
|
60
|
+
end.keys
|
61
|
+
host = connected_hosts.first
|
62
|
+
if !connected_hosts.first
|
63
|
+
err "Couldn't find any connected hosts"
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "#{Time.now}: Configuring all ESX hosts ..."
|
67
|
+
loghost = "udp://#{ip}:514"
|
68
|
+
hosts.map do |host|
|
69
|
+
Thread.new do
|
70
|
+
begin
|
71
|
+
c1 = conn.spawn_additional_connection
|
72
|
+
host = host.dup_on_conn(c1)
|
73
|
+
hostName = host.name
|
74
|
+
lock.synchronize do
|
75
|
+
puts "#{Time.now}: Configuring syslog on #{hostName}"
|
76
|
+
end
|
77
|
+
syslog = host.esxcli.system.syslog
|
78
|
+
syslog.config.set(:loghost => loghost)
|
79
|
+
syslog.reload
|
80
|
+
rescue Exception => ex
|
81
|
+
puts "#{Time.now}: #{host.name}: Got exception: #{ex.class}: #{ex.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end.each{|t| t.join}
|
85
|
+
puts "#{Time.now}: Done configuring syslog on all hosts"
|
86
|
+
|
87
|
+
local = "#{File.dirname(__FILE__)}/configurevCloudSuiteSyslog.sh"
|
88
|
+
osType = conn.serviceContent.about.osType
|
89
|
+
if File.exists?(local) && osType == "linux-x64"
|
90
|
+
puts "#{Time.now}: Configuring VCVA ..."
|
91
|
+
Net::SSH.start(conn.host, "root", :password => opts[:vc_root_pwd],
|
92
|
+
:paranoid => false) do |ssh|
|
93
|
+
ssh.scp.upload!(local, "/tmp/configurevCloudSuiteSyslog.sh")
|
94
|
+
cmd = "sh /tmp/configurevCloudSuiteSyslog.sh vcsa #{ip}"
|
95
|
+
puts "#{Time.now}: Running '#{cmd}' on VCVA"
|
96
|
+
puts ssh.exec!(cmd)
|
97
|
+
end
|
98
|
+
puts "#{Time.now}: Done with VC"
|
99
|
+
else
|
100
|
+
puts "#{Time.now}: VC isn't Linux, skipping ..."
|
101
|
+
end
|
102
|
+
puts "#{Time.now}: Done"
|
103
|
+
end
|
data/lib/rvc/modules/vds.rb
CHANGED
@@ -636,10 +636,10 @@ end
|
|
636
636
|
opts :create_vmknic do
|
637
637
|
summary "Create a vmknic on vDS on one or more hosts. Always uses DHCP"
|
638
638
|
arg :portgroup, nil, :lookup => VIM::Network
|
639
|
-
arg :
|
639
|
+
arg :hosts, nil, :lookup => VIM::HostSystem, :multi => true
|
640
640
|
end
|
641
641
|
|
642
|
-
def create_vmknic portgroup, hosts
|
642
|
+
def create_vmknic portgroup, hosts
|
643
643
|
if !portgroup.is_a?(VIM::DistributedVirtualPortgroup)
|
644
644
|
err "Legacy switches not supported yet"
|
645
645
|
end
|
@@ -660,3 +660,60 @@ def create_vmknic portgroup, hosts, opts
|
|
660
660
|
puts "Host #{host.name}: Added vmknic #{vmknic_name}"
|
661
661
|
end
|
662
662
|
end
|
663
|
+
|
664
|
+
opts :migrate_vmknic do
|
665
|
+
summary "Migrate a vmknic from a standard vSwitch to a vDS."
|
666
|
+
arg :host, nil, :lookup => VIM::HostSystem
|
667
|
+
arg :vmknic, nil
|
668
|
+
arg :portgroup, nil, :lookup => VIM::Network
|
669
|
+
end
|
670
|
+
|
671
|
+
def migrate_vmknic host, vmknic, portgroup
|
672
|
+
if !portgroup.is_a?(VIM::DistributedVirtualPortgroup)
|
673
|
+
err "Only migration from standard to a distributed vSwitch is supported for now."
|
674
|
+
end
|
675
|
+
ns = host.configManager.networkSystem
|
676
|
+
|
677
|
+
# Find the VMkernel interface.
|
678
|
+
vmk_dev = nil
|
679
|
+
ns.networkInfo.vnic.each do |vnic|
|
680
|
+
if vnic.device == vmknic
|
681
|
+
vmk_dev = vnic
|
682
|
+
break
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
# Find the source portgroup.
|
687
|
+
src_pg = nil
|
688
|
+
ns.networkInfo.portgroup.each do |pg|
|
689
|
+
if pg.spec.name == vmk_dev.portgroup
|
690
|
+
src_pg = pg
|
691
|
+
break
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
ns.UpdateNetworkConfig(
|
696
|
+
changeMode: 'modify',
|
697
|
+
config: VIM::HostNetworkConfig(
|
698
|
+
portgroup: [
|
699
|
+
VIM::HostPortGroupConfig(
|
700
|
+
changeOperation: 'remove',
|
701
|
+
spec: src_pg.spec
|
702
|
+
)
|
703
|
+
],
|
704
|
+
vnic: [
|
705
|
+
VIM::HostVirtualNicConfig(
|
706
|
+
changeOperation: 'edit',
|
707
|
+
device: vmknic,
|
708
|
+
portgroup: src_pg.spec.name,
|
709
|
+
spec: VIM::HostVirtualNicSpec(
|
710
|
+
distributedVirtualPort: VIM::DistributedVirtualSwitchPortConnection(
|
711
|
+
portgroupKey: portgroup.key,
|
712
|
+
switchUuid: portgroup.config.distributedVirtualSwitch.uuid
|
713
|
+
)
|
714
|
+
)
|
715
|
+
)
|
716
|
+
]
|
717
|
+
)
|
718
|
+
)
|
719
|
+
end
|
data/lib/rvc/modules/vim.rb
CHANGED
data/lib/rvc/modules/vm.rb
CHANGED
@@ -103,7 +103,25 @@ opts :shutdown_guest do
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def shutdown_guest vms, opts
|
106
|
-
vms.
|
106
|
+
conn = vms.first._connection
|
107
|
+
pc = conn.propertyCollector
|
108
|
+
vmsProps = pc.collectMultiple(vms, 'runtime.powerState',
|
109
|
+
'guest.toolsRunningStatus', 'name')
|
110
|
+
vms.each do |vm|
|
111
|
+
if vmsProps[vm]['runtime.powerState'] != 'poweredOn'
|
112
|
+
puts "VM #{vmsProps[vm]['name']} not powered on, skipping"
|
113
|
+
next
|
114
|
+
end
|
115
|
+
if vmsProps[vm]['guest.toolsRunningStatus'] != 'guestToolsRunning'
|
116
|
+
puts "VM #{vmsProps[vm]['name']} doesn't have tools running, skipping"
|
117
|
+
next
|
118
|
+
end
|
119
|
+
begin
|
120
|
+
vm.ShutdownGuest
|
121
|
+
rescue Exception => ex
|
122
|
+
puts "VM #{vmsProps[vm]['name']}: #{ex.class}: #{ex.message}"
|
123
|
+
end
|
124
|
+
end
|
107
125
|
wait_for_shutdown vms, opts unless opts[:timeout].nil?
|
108
126
|
end
|
109
127
|
|
@@ -274,7 +292,16 @@ rvc_alias :kill
|
|
274
292
|
rvc_alias :kill, :k
|
275
293
|
|
276
294
|
def kill vms
|
277
|
-
on_vms =
|
295
|
+
on_vms = []
|
296
|
+
vms.each do |vm|
|
297
|
+
begin
|
298
|
+
if vm.summary.runtime.powerState == 'poweredOn'
|
299
|
+
on_vms.push(vm)
|
300
|
+
end
|
301
|
+
rescue Exception => ex
|
302
|
+
puts "vm.rb:kill: Skipping current object #{vm} due to Exception: #{ex.message}"
|
303
|
+
end
|
304
|
+
end
|
278
305
|
off on_vms unless on_vms.empty?
|
279
306
|
shell.cmds.basic.destroy vms unless vms.empty?
|
280
307
|
end
|
@@ -516,6 +543,7 @@ opts :clone do
|
|
516
543
|
opt :host, "Host", :short => 'o', :type => :string, :lookup => VIM::HostSystem
|
517
544
|
opt :template, "Create a template", :short => 't'
|
518
545
|
opt :linked, "Create a linked clone", :short => 'l'
|
546
|
+
opt :customizationspecname, "Customization spec", :type => :string, :short => 's'
|
519
547
|
opt :power_on, "Power on VM after clone"
|
520
548
|
end
|
521
549
|
|
@@ -528,9 +556,19 @@ def clone src, dst, opts
|
|
528
556
|
diskMoveType = :moveChildMostDiskBacking
|
529
557
|
end
|
530
558
|
|
559
|
+
if opts[:customizationspecname]
|
560
|
+
begin
|
561
|
+
spec = src._connection.serviceContent.customizationSpecManager.GetCustomizationSpec(:name => opts[:customizationspecname]).spec
|
562
|
+
rescue
|
563
|
+
print "Customization Spec '#{opts[:customizationspecname]}' not found\n"
|
564
|
+
return
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
531
568
|
task = src.CloneVM_Task(:folder => folder,
|
532
569
|
:name => name,
|
533
570
|
:spec => {
|
571
|
+
:customization => spec,
|
534
572
|
:location => {
|
535
573
|
:diskMoveType => diskMoveType,
|
536
574
|
:host => opts[:host],
|
@@ -574,15 +612,26 @@ def annotate vm, str
|
|
574
612
|
vm.ReconfigVM_Task(:spec => { :annotation => str }).wait_for_completion
|
575
613
|
end
|
576
614
|
|
577
|
-
|
578
615
|
opts :modify_cpu do
|
579
616
|
summary "Change CPU configuration"
|
580
617
|
arg :vm, nil, :lookup => VIM::VirtualMachine
|
581
618
|
opt :num, "New number of CPUs", :type => :int, :required => true
|
619
|
+
opt :reservation, "Reservation in Mhz", :type => :int
|
620
|
+
opt :limit, "Limit in Mhz. -1 means no limit", :type => :int
|
582
621
|
end
|
583
622
|
|
584
623
|
def modify_cpu vm, opts
|
585
|
-
|
624
|
+
alloc, = vm.collect 'config.cpuAllocation'
|
625
|
+
if opts[:reservation]
|
626
|
+
alloc.reservation = opts[:reservation]
|
627
|
+
end
|
628
|
+
if opts[:limit]
|
629
|
+
alloc.limit = opts[:limit]
|
630
|
+
end
|
631
|
+
spec = {
|
632
|
+
:numCPUs => opts[:num],
|
633
|
+
:cpuAllocation => alloc,
|
634
|
+
}
|
586
635
|
tasks [vm], :ReconfigVM, :spec => spec
|
587
636
|
end
|
588
637
|
|
@@ -591,12 +640,26 @@ opts :modify_memory do
|
|
591
640
|
summary "Change memory configuration"
|
592
641
|
arg :vm, nil, :lookup => VIM::VirtualMachine
|
593
642
|
opt :size, "New memory size in MB", :type => :int, :required => true
|
643
|
+
opt :reservation, "Reservation in MB", :type => :int
|
644
|
+
opt :limit, "Limit in MB. -1 means no limit", :type => :int
|
594
645
|
end
|
595
646
|
|
596
647
|
def modify_memory vm, opts
|
597
|
-
err "VM needs to be off" unless vm.summary.runtime.powerState == 'poweredOff'
|
598
648
|
err "memory must be a multiple of 4MB" unless ( opts[:size] % 4 ) == 0
|
599
|
-
|
649
|
+
|
650
|
+
#alloc, = vm.collect 'config.memoryAllocation'
|
651
|
+
alloc = VIM::ResourceAllocationInfo()
|
652
|
+
if opts[:reservation]
|
653
|
+
alloc.reservation = opts[:reservation]
|
654
|
+
end
|
655
|
+
if opts[:limit]
|
656
|
+
alloc.limit = opts[:limit]
|
657
|
+
end
|
658
|
+
|
659
|
+
spec = {
|
660
|
+
:memoryMB => opts[:size],
|
661
|
+
:memoryAllocation => alloc,
|
662
|
+
}
|
600
663
|
tasks [vm], :ReconfigVM, :spec => spec
|
601
664
|
end
|
602
665
|
|
@@ -630,7 +693,7 @@ def vm_ip vm
|
|
630
693
|
|
631
694
|
ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
|
632
695
|
summary.guest.ipAddress
|
633
|
-
elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note
|
696
|
+
elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note['ip']
|
634
697
|
note['ip']
|
635
698
|
else
|
636
699
|
err "no IP known for this VM"
|
data/lib/rvc/modules/vm_guest.rb
CHANGED
@@ -1,30 +1,54 @@
|
|
1
|
+
# Copyright (c) 2013 VMware, Inc. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'rvc/vim'
|
22
|
+
|
1
23
|
opts :authenticate do
|
2
24
|
summary "Authenticate within guest"
|
3
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
25
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
4
26
|
opt :interactive_session, "Allow command to interact with desktop", :default => false, :type => :bool
|
5
27
|
opt :password, "Password in guest", :type => :string
|
6
28
|
opt :username, "Username in guest", :default => "root", :type => :string
|
7
29
|
end
|
8
30
|
|
9
|
-
def authenticate
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
opts[:password]
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
def authenticate vms, opts
|
32
|
+
vms.each do |vm|
|
33
|
+
auth = ((@auths ||= {})[vm] ||= {})[opts[:username]]
|
34
|
+
|
35
|
+
if opts[:password].nil? or opts[:password].empty?
|
36
|
+
opts[:password] = ask("password: ") { |q| q.echo = false }
|
37
|
+
end
|
38
|
+
|
39
|
+
auth = VIM.NamePasswordAuthentication(
|
40
|
+
:username => opts[:username],
|
41
|
+
:password => opts[:password],
|
42
|
+
:interactiveSession => opts[:interactive_session]
|
43
|
+
)
|
44
|
+
|
45
|
+
@auths[vm][opts[:username]] = auth
|
46
|
+
begin
|
47
|
+
check_auth vm, opts
|
48
|
+
rescue
|
49
|
+
clear_auth vm, opts
|
50
|
+
err "Could not authenticate: #{$!}"
|
51
|
+
end
|
28
52
|
end
|
29
53
|
end
|
30
54
|
|
@@ -240,6 +264,67 @@ def rmfile vm, opts
|
|
240
264
|
)
|
241
265
|
end
|
242
266
|
|
267
|
+
def generic_http_download uri, local_path
|
268
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
269
|
+
http.use_ssl = true
|
270
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
271
|
+
#http.set_debug_output $stderr
|
272
|
+
http.start
|
273
|
+
|
274
|
+
# headers = { 'cookie' => connection.cookie }
|
275
|
+
headers = {}
|
276
|
+
http_path = "#{uri.path}?#{uri.query}"
|
277
|
+
http.request_get(http_path, headers) do |res|
|
278
|
+
case res
|
279
|
+
when Net::HTTPOK
|
280
|
+
len = res.content_length
|
281
|
+
count = 0
|
282
|
+
File.open(local_path, 'wb') do |io|
|
283
|
+
res.read_body do |segment|
|
284
|
+
count += segment.length
|
285
|
+
io.write segment
|
286
|
+
$stdout.write "\e[0G\e[Kdownloading #{count}/#{len} bytes (#{(count*100)/len}%)"
|
287
|
+
$stdout.flush
|
288
|
+
end
|
289
|
+
end
|
290
|
+
$stdout.puts
|
291
|
+
else
|
292
|
+
err "download failed: #{res.message}"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def generic_http_upload local_path, uri, size = nil
|
298
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
299
|
+
http.use_ssl = true
|
300
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
301
|
+
#http.set_debug_output $stderr
|
302
|
+
http.start
|
303
|
+
|
304
|
+
|
305
|
+
open(local_path, 'rb') do |io|
|
306
|
+
stream = ProgressStream.new(io, size || io.stat.size) do |s|
|
307
|
+
$stdout.write "\e[0G\e[Kuploading #{s.count}/#{s.len} bytes (#{(s.count*100)/s.len}%)"
|
308
|
+
$stdout.flush
|
309
|
+
end
|
310
|
+
|
311
|
+
headers = {
|
312
|
+
'content-length' => (size || io.stat.size).to_s,
|
313
|
+
'Content-Type' => 'application/octet-stream',
|
314
|
+
}
|
315
|
+
http_path = "#{uri.path}?#{uri.query}"
|
316
|
+
|
317
|
+
request = Net::HTTP::Put.new http_path, headers
|
318
|
+
request.body_stream = stream
|
319
|
+
res = http.request(request)
|
320
|
+
$stdout.puts
|
321
|
+
case res
|
322
|
+
when Net::HTTPOK
|
323
|
+
else
|
324
|
+
err "upload failed: #{res.message}"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
243
328
|
|
244
329
|
opts :download_file do
|
245
330
|
summary "Download file from guest"
|
@@ -265,14 +350,14 @@ def download_file vm, opts
|
|
265
350
|
|
266
351
|
download_uri = URI.parse(download_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
|
267
352
|
download_path = "#{download_uri.path}?#{download_uri.query}"
|
268
|
-
|
269
|
-
|
353
|
+
|
354
|
+
generic_http_download download_uri, opts[:local_path]
|
270
355
|
end
|
271
356
|
|
272
357
|
|
273
358
|
opts :upload_file do
|
274
359
|
summary "Upload file to guest"
|
275
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
360
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
276
361
|
opt :group_id, "Group ID of file", :type => :int
|
277
362
|
opt :guest_path, "Path in guest to upload to", :required => true, :type => :string
|
278
363
|
opt :local_path, "Local file to upload", :required => true, :type => :string
|
@@ -282,35 +367,45 @@ opts :upload_file do
|
|
282
367
|
opt :username, "Username in guest", :default => "root", :type => :string
|
283
368
|
end
|
284
369
|
|
285
|
-
def upload_file
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
370
|
+
def upload_file vms, opts
|
371
|
+
vms.each do |vm|
|
372
|
+
guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
|
373
|
+
err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
|
374
|
+
fileManager = guestOperationsManager.fileManager
|
375
|
+
|
376
|
+
opts[:permissions] = opts[:permissions].to_i(8) if opts[:permissions]
|
377
|
+
|
378
|
+
auth = get_auth vm, opts
|
379
|
+
|
380
|
+
file_size = nil
|
381
|
+
if opts[:local_path] =~ /^http:/
|
382
|
+
# XXX: Not acceptable for big files
|
383
|
+
file_size = open(opts[:local_path], 'rb').read.length
|
384
|
+
else
|
385
|
+
err "local file does not exist" unless File.exists? local_path
|
386
|
+
file = File.new(opts[:local_path], 'rb')
|
387
|
+
file_size = file.size
|
388
|
+
end
|
389
|
+
|
390
|
+
upload_url = fileManager.
|
391
|
+
InitiateFileTransferToGuest(
|
392
|
+
:vm => vm,
|
393
|
+
:auth => auth,
|
394
|
+
:guestFilePath => opts[:guest_path],
|
395
|
+
:fileAttributes => VIM.GuestPosixFileAttributes(
|
396
|
+
:groupId => opts[:group_id],
|
397
|
+
:ownerId => opts[:owner_id],
|
398
|
+
:permissions => opts[:permissions]
|
399
|
+
),
|
400
|
+
:fileSize => file_size,
|
401
|
+
:overwrite => opts[:overwrite]
|
402
|
+
)
|
403
|
+
|
404
|
+
upload_uri = URI.parse(upload_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
|
405
|
+
upload_path = "#{upload_uri.path}?#{upload_uri.query}"
|
406
|
+
|
407
|
+
generic_http_upload opts[:local_path], upload_uri, file_size
|
408
|
+
end
|
314
409
|
end
|
315
410
|
|
316
411
|
|
@@ -431,7 +526,7 @@ end
|
|
431
526
|
# Process commands
|
432
527
|
opts :start_program do
|
433
528
|
summary "Run program in guest"
|
434
|
-
arg :vm, nil, :lookup => VIM::VirtualMachine
|
529
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
435
530
|
opt :arguments, "Arguments of command", :default => "", :type => :string
|
436
531
|
opt :background, "Don't wait for process to finish", :default => false, :type => :bool
|
437
532
|
opt :delay, "Interval in seconds", :type => :float, :default => 5.0
|
@@ -444,47 +539,51 @@ opts :start_program do
|
|
444
539
|
conflicts :background, :delay
|
445
540
|
end
|
446
541
|
|
447
|
-
def start_program
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
:
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
)
|
465
|
-
|
466
|
-
Timeout.timeout opts[:timeout] do
|
467
|
-
while true
|
468
|
-
processes = processManager.
|
469
|
-
ListProcessesInGuest(
|
470
|
-
:vm => vm,
|
471
|
-
:auth => auth,
|
472
|
-
:pids => [pid]
|
542
|
+
def start_program vms, opts
|
543
|
+
vms.each do |vm|
|
544
|
+
guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
|
545
|
+
err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :processManager
|
546
|
+
processManager = guestOperationsManager.processManager
|
547
|
+
|
548
|
+
auth = get_auth vm, opts
|
549
|
+
|
550
|
+
pid = processManager.
|
551
|
+
StartProgramInGuest(
|
552
|
+
:vm => vm,
|
553
|
+
:auth => auth,
|
554
|
+
:spec => VIM.GuestProgramSpec(
|
555
|
+
:arguments => opts[:arguments],
|
556
|
+
:programPath => opts[:program_path],
|
557
|
+
:envVariables => opts[:env],
|
558
|
+
:workingDirectory => opts[:working_directory]
|
473
559
|
)
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
560
|
+
)
|
561
|
+
|
562
|
+
begin
|
563
|
+
Timeout.timeout opts[:timeout] do
|
564
|
+
while true
|
565
|
+
processes = processManager.
|
566
|
+
ListProcessesInGuest(
|
567
|
+
:vm => vm,
|
568
|
+
:auth => auth,
|
569
|
+
:pids => [pid]
|
570
|
+
)
|
571
|
+
process = processes.first
|
572
|
+
|
573
|
+
if !process.endTime.nil?
|
574
|
+
if process.exitCode != 0
|
575
|
+
err "Process failed with exit code #{process.exitCode}"
|
576
|
+
end
|
577
|
+
break
|
578
|
+
elsif opts[:background]
|
579
|
+
break
|
580
|
+
end
|
581
|
+
|
582
|
+
sleep opts[:delay]
|
479
583
|
end
|
480
|
-
break
|
481
|
-
elsif opts[:background]
|
482
|
-
break
|
483
584
|
end
|
484
|
-
|
485
|
-
|
585
|
+
rescue Timeout::Error
|
586
|
+
err "Timed out waiting for process to finish."
|
486
587
|
end
|
487
588
|
end
|
488
|
-
rescue Timeout::Error
|
489
|
-
err "Timed out waiting for process to finish."
|
490
589
|
end
|