rvc 1.5.0 → 1.6.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/README.rdoc +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/rvc +53 -9
- data/lib/rvc/completion.rb +57 -19
- data/lib/rvc/extensions/ComputeResource.rb +2 -2
- data/lib/rvc/extensions/DVPortSetting.rb +108 -0
- data/lib/rvc/extensions/Datacenter.rb +19 -4
- data/lib/rvc/extensions/Datastore.rb +6 -1
- data/lib/rvc/extensions/DistributedVirtualPort.rb +146 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +274 -10
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +124 -3
- data/lib/rvc/extensions/Folder.rb +9 -2
- data/lib/rvc/extensions/HostSystem.rb +60 -0
- data/lib/rvc/extensions/ManagedEntity.rb +19 -0
- data/lib/rvc/extensions/ParaVirtualSCSIController.rb +25 -0
- data/lib/rvc/extensions/PerfCounterInfo.rb +26 -0
- data/lib/rvc/extensions/PerformanceManager.rb +83 -0
- data/lib/rvc/extensions/ResourcePool.rb +21 -0
- data/lib/rvc/extensions/VirtualDevice.rb +59 -0
- data/lib/rvc/extensions/VirtualDisk.rb +25 -0
- data/lib/rvc/extensions/VirtualEthernetCard.rb +32 -0
- data/lib/rvc/extensions/VirtualMachine.rb +112 -1
- data/lib/rvc/field.rb +122 -0
- data/lib/rvc/filesystem_session.rb +20 -0
- data/lib/rvc/inventory.rb +35 -12
- data/lib/rvc/known_hosts.rb +20 -0
- data/lib/rvc/memory_session.rb +20 -0
- data/lib/rvc/modules.rb +67 -7
- data/lib/rvc/modules/alarm.rb +37 -0
- data/lib/rvc/modules/basic.rb +172 -41
- data/lib/rvc/modules/cluster.rb +18 -2
- data/lib/rvc/modules/core.rb +63 -0
- data/lib/rvc/modules/datastore.rb +158 -0
- data/lib/rvc/modules/device.rb +275 -0
- data/lib/rvc/modules/esxcli.rb +193 -0
- data/lib/rvc/modules/find.rb +125 -0
- data/lib/rvc/modules/issue.rb +33 -0
- data/lib/rvc/modules/perf.rb +284 -0
- data/lib/rvc/modules/permissions.rb +20 -0
- data/lib/rvc/modules/resource_pool.rb +69 -0
- data/lib/rvc/modules/role.rb +23 -3
- data/lib/rvc/modules/snapshot.rb +20 -0
- data/lib/rvc/modules/vds.rb +605 -0
- data/lib/rvc/modules/vim.rb +103 -26
- data/lib/rvc/modules/vm.rb +93 -220
- data/lib/rvc/modules/vnc.rb +50 -13
- data/lib/rvc/option_parser.rb +50 -2
- data/lib/rvc/readline-ffi.rb +2 -1
- data/lib/rvc/shell.rb +34 -33
- data/lib/rvc/util.rb +120 -2
- data/test/test_fs.rb +9 -5
- data/test/test_metric.rb +79 -0
- metadata +33 -3
data/lib/rvc/modules/cluster.rb
CHANGED
@@ -48,9 +48,9 @@ def add_host cluster, hostname, opts
|
|
48
48
|
:sslThumbprint => sslThumbprint,
|
49
49
|
}
|
50
50
|
task = cluster.AddHost_Task :spec => spec,
|
51
|
-
:asConnected =>
|
51
|
+
:asConnected => true
|
52
52
|
begin
|
53
|
-
task
|
53
|
+
one_progress task
|
54
54
|
break
|
55
55
|
rescue VIM::SSLVerifyFault
|
56
56
|
puts "SSL thumbprint: #{$!.fault.thumbprint}"
|
@@ -62,3 +62,19 @@ def add_host cluster, hostname, opts
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
|
+
|
66
|
+
|
67
|
+
opts :configure_ha do
|
68
|
+
summary "Configure HA on a cluster"
|
69
|
+
arg :cluster, nil, :lookup => VIM::ClusterComputeResource
|
70
|
+
opt :disabled, "Disable HA", :default => false
|
71
|
+
end
|
72
|
+
|
73
|
+
def configure_ha cluster, opts
|
74
|
+
spec = VIM::ClusterConfigSpecEx(
|
75
|
+
:dasConfig => {
|
76
|
+
:enabled => !opts[:disabled],
|
77
|
+
}
|
78
|
+
)
|
79
|
+
one_progress(cluster.ReconfigureComputeResource_Task :spec => spec, :modify => true)
|
80
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright (c) 2011 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
|
+
opts :quit do
|
22
|
+
summary "Exit RVC"
|
23
|
+
end
|
24
|
+
|
25
|
+
rvc_alias :quit
|
26
|
+
rvc_alias :quit, :exit
|
27
|
+
rvc_alias :quit, :q
|
28
|
+
|
29
|
+
def quit
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
opts :reload do
|
35
|
+
summary "Reload RVC command modules and extensions"
|
36
|
+
opt :verbose, "Display filenames loaded", :short => 'v', :default => false
|
37
|
+
end
|
38
|
+
|
39
|
+
rvc_alias :reload
|
40
|
+
|
41
|
+
def reload opts
|
42
|
+
old_verbose = $VERBOSE
|
43
|
+
$VERBOSE = nil unless opts[:verbose]
|
44
|
+
|
45
|
+
RVC.reload_modules opts[:verbose]
|
46
|
+
|
47
|
+
typenames = Set.new(VIM.loader.typenames.select { |x| VIM.const_defined? x })
|
48
|
+
dirs = VIM.extension_dirs
|
49
|
+
dirs.each do |path|
|
50
|
+
Dir.open(path) do |dir|
|
51
|
+
dir.each do |file|
|
52
|
+
next unless file =~ /\.rb$/
|
53
|
+
next unless typenames.member? $`
|
54
|
+
file_path = File.join(dir, file)
|
55
|
+
puts "loading #{$`} extensions from #{file_path}" if opts[:verbose]
|
56
|
+
load file_path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
ensure
|
62
|
+
$VERBOSE = old_verbose
|
63
|
+
end
|
@@ -18,6 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'terminal-table/import'
|
22
|
+
|
21
23
|
opts :download do
|
22
24
|
summary "Download a file from a datastore"
|
23
25
|
arg 'datastore-path', "Filename on the datastore", :lookup => VIM::Datastore::FakeDatastoreFile
|
@@ -163,3 +165,159 @@ end
|
|
163
165
|
def http_path dc_name, ds_name, path
|
164
166
|
"/folder/#{URI.escape path}?dcPath=#{URI.escape dc_name}&dsName=#{URI.escape ds_name}"
|
165
167
|
end
|
168
|
+
|
169
|
+
|
170
|
+
opts :find_orphans do
|
171
|
+
summary "Finds directories on the datastore that don't belong to any registered VM"
|
172
|
+
arg :datastore, nil, :lookup => VIM::Datastore
|
173
|
+
opt :mark, "Name of the mark to save results in", :required => false, :type => :string
|
174
|
+
end
|
175
|
+
|
176
|
+
def find_orphans ds, opts
|
177
|
+
pc = ds._connection.serviceContent.propertyCollector
|
178
|
+
vms = ds.vm
|
179
|
+
|
180
|
+
puts "Collecting file information about #{vms.length} VMs ... (this may take a while)"
|
181
|
+
dsName = ds.name
|
182
|
+
vmFiles = pc.collectMultiple vms, 'layoutEx.file'
|
183
|
+
|
184
|
+
puts "Collecting file information on datastore '#{dsName}' ..."
|
185
|
+
dsBrowser = ds.browser
|
186
|
+
result = dsBrowser.SearchDatastore_Task(
|
187
|
+
:datastorePath => "[#{dsName}] ",
|
188
|
+
:searchSpec => {
|
189
|
+
:details => {
|
190
|
+
:fileType => true,
|
191
|
+
:fileSize => false,
|
192
|
+
:fileOwner => false,
|
193
|
+
:modification => false
|
194
|
+
}
|
195
|
+
}
|
196
|
+
).wait_for_completion
|
197
|
+
dsDirectories = result.file.grep(RbVmomi::VIM::FolderFileInfo).map(&:path)
|
198
|
+
|
199
|
+
puts "Checking for any VMs that got added inbetween ..."
|
200
|
+
addedVms = ds.vm - vms
|
201
|
+
if addedVms.length > 0
|
202
|
+
puts "Processing #{addedVms.length} new VMs ..."
|
203
|
+
vmFiles.merge!(pc.collectMultiple addedVms, 'layoutEx.file')
|
204
|
+
end
|
205
|
+
|
206
|
+
begin
|
207
|
+
perDSUsage = pc.collectMultiple vms, 'storage.perDatastoreUsage'
|
208
|
+
rescue RbVmomi::Fault => ex
|
209
|
+
if ex.fault.is_a?(RbVmomi::VIM::ManagedObjectNotFound)
|
210
|
+
vms = vms - [ex.fault.obj]
|
211
|
+
retry
|
212
|
+
end
|
213
|
+
perDSUsage = []
|
214
|
+
raise
|
215
|
+
end
|
216
|
+
|
217
|
+
puts "Cross-referencing VM files with files on datastore '#{dsName}' ..."
|
218
|
+
vmFilenameHash = Hash[vmFiles.map do |vm, info|
|
219
|
+
[
|
220
|
+
vm,
|
221
|
+
info["layoutEx.file"].map{|x| x.name}.select{|x| x =~ /^\[#{dsName}\] /}.map{|x| x.gsub(/^\[#{dsName}\] /, '')}
|
222
|
+
]
|
223
|
+
end]
|
224
|
+
filenames = vmFilenameHash.values.flatten(1)
|
225
|
+
vmDirectories = filenames.map{ |x| x.split('/').first }.uniq
|
226
|
+
orphanDirectories = (dsDirectories - vmDirectories).reject { |x| x =~ /^\./ }
|
227
|
+
puts "Found #{orphanDirectories.length} potentially orphaned directories"
|
228
|
+
|
229
|
+
puts "Composing list of potentially orphaned files ... (this may take a while)"
|
230
|
+
data = orphanDirectories.map do |dir|
|
231
|
+
begin
|
232
|
+
result = dsBrowser.SearchDatastoreSubFolders_Task(
|
233
|
+
:datastorePath => "[#{dsName}] #{dir}/",
|
234
|
+
:searchSpec => {
|
235
|
+
:details => {
|
236
|
+
:fileType => false,
|
237
|
+
:fileSize => true,
|
238
|
+
:fileOwner => false,
|
239
|
+
:modification => false
|
240
|
+
}
|
241
|
+
}
|
242
|
+
).wait_for_completion
|
243
|
+
files = result.map(&:file).flatten
|
244
|
+
dirSize = files.map(&:fileSize).sum
|
245
|
+
$stdout.write "."
|
246
|
+
$stdout.flush
|
247
|
+
[dir, dirSize, files.length]
|
248
|
+
rescue
|
249
|
+
puts "failed to search #{dir.inspect}: #{$!.message}"
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
end.compact
|
253
|
+
puts
|
254
|
+
puts
|
255
|
+
|
256
|
+
if data.empty?
|
257
|
+
puts "No orphans found"
|
258
|
+
else
|
259
|
+
puts(table do
|
260
|
+
data.sort_by { |a| a[1] }.each do |x|
|
261
|
+
dir, dirSize, numFiles = x
|
262
|
+
self.headings = 'Directory', 'Space Used', '# Files'
|
263
|
+
add_row [dir, "#{dirSize.metric}B", numFiles]
|
264
|
+
end
|
265
|
+
end)
|
266
|
+
end
|
267
|
+
|
268
|
+
puts
|
269
|
+
|
270
|
+
totalSize = data.map{|x| x[1]}.sum
|
271
|
+
dsSummary = ds.summary
|
272
|
+
vmDsUsage = perDSUsage.map{|vm, x| x['storage.perDatastoreUsage'].find{|y| y.datastore == ds}}.reject{|x| x == nil}
|
273
|
+
committed = vmDsUsage.map{|x| x.committed}.sum
|
274
|
+
unshared = vmDsUsage.map{|x| x.unshared}.sum
|
275
|
+
otherSpace = (dsSummary.capacity - dsSummary.freeSpace) - unshared
|
276
|
+
puts "Provisioned on Datastore: #{dsSummary.uncommitted.metric}B"
|
277
|
+
puts "Capacity of Datastore: #{dsSummary.capacity.metric}B"
|
278
|
+
puts "Free Space on Datastore: #{dsSummary.freeSpace.metric}B"
|
279
|
+
puts "VMs Provisioned on Datastore: #{vmDsUsage.map(&:uncommitted).sum.metric}B"
|
280
|
+
puts "VMs Used on Datastore: #{committed.metric}B"
|
281
|
+
puts "VMs Unshared on Datastore: #{vmDsUsage.map(&:unshared).sum.metric}B"
|
282
|
+
puts "Unaccounted space: #{otherSpace.metric}B"
|
283
|
+
puts "Total size of detected potential orphans: #{totalSize.metric}B"
|
284
|
+
puts
|
285
|
+
|
286
|
+
results = data.map do |dirInfo|
|
287
|
+
RbVmomi::VIM::Datastore::FakeDatastoreFolder.new(ds, "#{dirInfo[0]}")
|
288
|
+
end
|
289
|
+
opts[:mark] ||= "#{dsName}_orphans"
|
290
|
+
CMD.mark.mark opts[:mark], results
|
291
|
+
puts "Saved results to mark '#{opts[:mark]}'"
|
292
|
+
|
293
|
+
i = 0
|
294
|
+
results.each do |r|
|
295
|
+
display_path = r.path
|
296
|
+
puts "#{i} #{display_path}"
|
297
|
+
CMD.mark.mark i.to_s, [r]
|
298
|
+
i += 1
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
opts :delete do
|
304
|
+
summary "Deletes the specified files or folders from the datastore"
|
305
|
+
arg :objs, nil, :multi => true, :lookup => RVC::InventoryObject
|
306
|
+
end
|
307
|
+
|
308
|
+
def delete objs
|
309
|
+
fm = nil
|
310
|
+
tasks = objs.map do |obj|
|
311
|
+
isFolder = obj.is_a?(RbVmomi::VIM::Datastore::FakeDatastoreFolder)
|
312
|
+
isFile = obj.is_a?(RbVmomi::VIM::Datastore::FakeDatastoreFile)
|
313
|
+
err "Parameter is neither file nor folder" if !isFolder && !isFile
|
314
|
+
|
315
|
+
ds = obj.datastore
|
316
|
+
dc = ds.path.find{|x| x[0].is_a? RbVmomi::VIM::Datacenter}[0]
|
317
|
+
fm ||= ds._connection.serviceContent.fileManager
|
318
|
+
dsPath = "[#{ds.name}] #{obj.path}"
|
319
|
+
puts "Deleting #{dsPath}"
|
320
|
+
fm.DeleteDatastoreFile_Task(:name => dsPath, :datacenter => dc)
|
321
|
+
end
|
322
|
+
progress(tasks)
|
323
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
opts :connect do
|
2
|
+
summary "Connect a virtual device"
|
3
|
+
arg :device, nil, :lookup => VIM::VirtualDevice, :multi => true
|
4
|
+
end
|
5
|
+
|
6
|
+
def connect devs
|
7
|
+
change_devices_connectivity devs, true
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
opts :disconnect do
|
12
|
+
summary "Disconnect a virtual device"
|
13
|
+
arg :device, nil, :lookup => VIM::VirtualDevice, :multi => true
|
14
|
+
end
|
15
|
+
|
16
|
+
def disconnect devs
|
17
|
+
change_devices_connectivity devs, false
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
opts :remove do
|
22
|
+
summary "Remove a virtual device"
|
23
|
+
arg :device, nil, :lookup => VIM::VirtualDevice, :multi => true
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove devs
|
27
|
+
vm_devs = devs.group_by(&:rvc_vm)
|
28
|
+
tasks = vm_devs.map do |vm,my_devs|
|
29
|
+
device_changes = my_devs.map do |dev|
|
30
|
+
fileOp = dev.backing.is_a?(VIM::VirtualDeviceFileBackingInfo) ? 'destroy' : nil
|
31
|
+
{ :operation => :remove, :fileOperation => fileOp, :device => dev }
|
32
|
+
end
|
33
|
+
spec = { :deviceChange => device_changes }
|
34
|
+
vm.ReconfigVM_Task(:spec => spec)
|
35
|
+
end
|
36
|
+
|
37
|
+
progress tasks
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
opts :add_net do
|
42
|
+
summary "Add a network adapter to a virtual machine"
|
43
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
44
|
+
arg :network, nil, :lookup => VIM::Network
|
45
|
+
opt :type, "Adapter type", :default => 'e1000'
|
46
|
+
end
|
47
|
+
|
48
|
+
NET_DEVICE_CLASSES = {
|
49
|
+
'e1000' => VIM::VirtualE1000,
|
50
|
+
'vmxnet3' => VIM::VirtualVmxnet3,
|
51
|
+
}
|
52
|
+
|
53
|
+
def add_net vm, network, opts
|
54
|
+
klass = NET_DEVICE_CLASSES[opts[:type]] or err "unknown network adapter type #{opts[:type].inspect}"
|
55
|
+
|
56
|
+
case network
|
57
|
+
when VIM::DistributedVirtualPortgroup
|
58
|
+
switch, pg_key = network.collect 'config.distributedVirtualSwitch', 'key'
|
59
|
+
port = VIM.DistributedVirtualSwitchPortConnection(
|
60
|
+
:switchUuid => switch.uuid,
|
61
|
+
:portgroupKey => pg_key)
|
62
|
+
summary = network.name
|
63
|
+
backing = VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo(:port => port)
|
64
|
+
when VIM::Network
|
65
|
+
summary = network.name
|
66
|
+
backing = VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => network.name)
|
67
|
+
else fail
|
68
|
+
end
|
69
|
+
|
70
|
+
_add_device vm, nil, klass.new(
|
71
|
+
:key => -1,
|
72
|
+
:deviceInfo => {
|
73
|
+
:summary => summary,
|
74
|
+
:label => "",
|
75
|
+
},
|
76
|
+
:backing => backing,
|
77
|
+
:addressType => 'generated'
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
opts :add_disk do
|
83
|
+
summary "Add a hard drive to a virtual machine"
|
84
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
85
|
+
opt :size, 'Size', :default => '10G'
|
86
|
+
opt :controller, 'Virtual controller', :type => :string, :lookup => VIM::VirtualController
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_disk vm, opts
|
90
|
+
controller, unit_number = pick_controller vm, opts[:controller], [VIM::VirtualSCSIController, VIM::VirtualIDEController]
|
91
|
+
id = "disk-#{controller.key}-#{unit_number}"
|
92
|
+
filename = "#{File.dirname(vm.summary.config.vmPathName)}/#{id}.vmdk"
|
93
|
+
_add_device vm, :create, VIM::VirtualDisk(
|
94
|
+
:key => -1,
|
95
|
+
:backing => VIM.VirtualDiskFlatVer2BackingInfo(
|
96
|
+
:fileName => filename,
|
97
|
+
:diskMode => :persistent,
|
98
|
+
:thinProvisioned => true
|
99
|
+
),
|
100
|
+
:capacityInKB => MetricNumber.parse(opts[:size]).to_i/1000,
|
101
|
+
:controllerKey => controller.key,
|
102
|
+
:unitNumber => unit_number
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
opts :add_cdrom do
|
108
|
+
summary "Add a cdrom drive"
|
109
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
110
|
+
opt :controller, 'Virtual controller', :type => :string, :lookup => VIM::VirtualIDEController
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_cdrom vm, opts
|
114
|
+
controller, unit_number = pick_controller vm, opts[:controller], [VIM::VirtualIDEController]
|
115
|
+
id = "cdrom-#{controller.key}-#{unit_number}"
|
116
|
+
_add_device vm, nil, VIM.VirtualCdrom(
|
117
|
+
:controllerKey => controller.key,
|
118
|
+
:key => -1,
|
119
|
+
:unitNumber => unit_number,
|
120
|
+
:backing => VIM.VirtualCdromAtapiBackingInfo(
|
121
|
+
:deviceName => id,
|
122
|
+
:useAutoDetect => false
|
123
|
+
),
|
124
|
+
:connectable => VIM.VirtualDeviceConnectInfo(
|
125
|
+
:allowGuestControl => true,
|
126
|
+
:connected => true,
|
127
|
+
:startConnected => true
|
128
|
+
)
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
opts :insert_cdrom do
|
134
|
+
summary "Put a disc in a virtual CDROM drive"
|
135
|
+
arg :dev, nil, :lookup => VIM::VirtualDevice
|
136
|
+
arg :iso, "Path to the ISO image on a datastore", :lookup => VIM::Datastore::FakeDatastoreFile
|
137
|
+
end
|
138
|
+
|
139
|
+
def insert_cdrom dev, iso
|
140
|
+
vm = dev.rvc_vm
|
141
|
+
backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)
|
142
|
+
|
143
|
+
spec = {
|
144
|
+
:deviceChange => [
|
145
|
+
{
|
146
|
+
:operation => :edit,
|
147
|
+
:device => dev.class.new(
|
148
|
+
:key => dev.key,
|
149
|
+
:controllerKey => dev.controllerKey,
|
150
|
+
:backing => backing)
|
151
|
+
}
|
152
|
+
]
|
153
|
+
}
|
154
|
+
|
155
|
+
progress [vm.ReconfigVM_Task(:spec => spec)]
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
SCSI_CONTROLLER_TYPES = {
|
160
|
+
'pvscsi' => VIM::ParaVirtualSCSIController,
|
161
|
+
'buslogic' => VIM::VirtualBusLogicController,
|
162
|
+
'lsilogic' => VIM::VirtualLsiLogicController,
|
163
|
+
'lsilogic-sas' => VIM::VirtualLsiLogicSASController,
|
164
|
+
}
|
165
|
+
|
166
|
+
SCSI_BUS_NUMBERS = [0, 1, 2, 3]
|
167
|
+
|
168
|
+
opts :add_scsi_controller do
|
169
|
+
summary "Add a virtual SCSI controller to a VM"
|
170
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
171
|
+
opt :type, SCSI_CONTROLLER_TYPES.keys*'/', :default => 'lsilogic' # TODO tab complete
|
172
|
+
opt :sharing, VIM::VirtualSCSISharing.values*'/', :default => 'noSharing' # TODO tab complete
|
173
|
+
opt :hot_add, "Enable hot-add/remove", :default => nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def add_scsi_controller vm, opts
|
177
|
+
klass = SCSI_CONTROLLER_TYPES[opts[:type]] or err "invalid SCSI controller type #{opts[:type].inspect}"
|
178
|
+
err "invalid value for --sharing" unless VIM::VirtualSCSISharing.values.member? opts[:sharing]
|
179
|
+
|
180
|
+
existing_devices, = vm.collect 'config.hardware.device'
|
181
|
+
used_bus_numbers = existing_devices.grep(VIM::VirtualSCSIController).map(&:busNumber)
|
182
|
+
bus_number = (SCSI_BUS_NUMBERS - used_bus_numbers).min
|
183
|
+
err "unable to allocate a bus number, too many SCSI controllers" unless bus_number
|
184
|
+
|
185
|
+
controller = klass.new(
|
186
|
+
:key => -1,
|
187
|
+
:busNumber => bus_number,
|
188
|
+
:sharedBus => opts[:sharing],
|
189
|
+
:hotAddRemove => opts[:hot_add]
|
190
|
+
)
|
191
|
+
|
192
|
+
_add_device vm, nil, controller
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
opts :add_serial do
|
197
|
+
summary "Add a virtual serial port to a VM"
|
198
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_serial vm
|
202
|
+
# create an initial no-op backing
|
203
|
+
backing = VIM::VirtualSerialPortURIBackingInfo(:direction => :client, :serviceURI => 'localhost:0')
|
204
|
+
_add_device vm, nil, VIM::VirtualSerialPort(:yieldOnPoll => true, :key => -1, :backing => backing)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
opts :connect_serial_uri do
|
209
|
+
summary "Connect a virtual serial port to the given network URI"
|
210
|
+
arg :dev, nil, :lookup => VIM::VirtualSerialPort
|
211
|
+
arg :uri, "URI", :type => :string
|
212
|
+
opt :client, "Connect to another machine", :short => 'c'
|
213
|
+
opt :server, "Listen for incoming connections", :short => 's'
|
214
|
+
conflicts :client, :server
|
215
|
+
end
|
216
|
+
|
217
|
+
def connect_serial_uri dev, uri, opts
|
218
|
+
err "must specify --client or --server" unless opts[:client] || opts[:server]
|
219
|
+
direction = opts[:client] ? 'client' : 'server'
|
220
|
+
dev = dev.dup
|
221
|
+
dev.backing = VIM::VirtualSerialPortURIBackingInfo(:direction => direction, :serviceURI => uri)
|
222
|
+
spec = { :deviceChange => [ { :operation => :edit, :device => dev } ] }
|
223
|
+
progress [dev.rvc_vm.ReconfigVM_Task(:spec => spec)]
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
def _add_device vm, fileOp, dev
|
228
|
+
spec = {
|
229
|
+
:deviceChange => [
|
230
|
+
{ :operation => :add, :fileOperation => fileOp, :device => dev },
|
231
|
+
]
|
232
|
+
}
|
233
|
+
task = vm.ReconfigVM_Task(:spec => spec)
|
234
|
+
result = progress([task])[task]
|
235
|
+
if result == nil
|
236
|
+
new_device = vm.collect('config.hardware.device')[0].grep(dev.class).last
|
237
|
+
puts "Added device #{new_device.name}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def change_devices_connectivity devs, connected
|
242
|
+
if dev = devs.find { |dev| dev.connectable.nil? }
|
243
|
+
err "#{dev.name} is not connectable."
|
244
|
+
end
|
245
|
+
|
246
|
+
vm_devs = devs.group_by(&:rvc_vm)
|
247
|
+
tasks = vm_devs.map do |vm,my_devs|
|
248
|
+
device_changes = my_devs.map do |dev|
|
249
|
+
dev = dev.dup
|
250
|
+
dev.connectable = dev.connectable.dup
|
251
|
+
dev.connectable.connected = connected
|
252
|
+
dev.connectable.startConnected = connected
|
253
|
+
{ :operation => :edit, :device => dev }
|
254
|
+
end
|
255
|
+
spec = { :deviceChange => device_changes }
|
256
|
+
vm.ReconfigVM_Task(:spec => spec)
|
257
|
+
end
|
258
|
+
|
259
|
+
progress tasks
|
260
|
+
end
|
261
|
+
|
262
|
+
def pick_controller vm, controller, controller_classes
|
263
|
+
existing_devices, = vm.collect 'config.hardware.device'
|
264
|
+
|
265
|
+
controller ||= existing_devices.find do |dev|
|
266
|
+
controller_classes.any? { |klass| dev.is_a? klass } &&
|
267
|
+
dev.device.length < 2
|
268
|
+
end
|
269
|
+
err "no suitable controller found" unless controller
|
270
|
+
|
271
|
+
used_unit_numbers = existing_devices.select { |dev| dev.controllerKey == controller.key }.map(&:unitNumber)
|
272
|
+
unit_number = (used_unit_numbers.max||-1) + 1
|
273
|
+
|
274
|
+
[controller, unit_number]
|
275
|
+
end
|