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.
@@ -72,7 +72,7 @@ def leaves roots, types = []
72
72
  nodes.each do |node|
73
73
  if (node.class.traverse? or roots.member? node) and
74
74
  (types & (node.field('type') || [])).empty?
75
- node.children.each { |k,v| v.rvc_link(node, k); new_nodes << v }
75
+ node.rvc_children.each { |k,v| v.rvc_link(node, k); new_nodes << v }
76
76
  else
77
77
  leaves << node
78
78
  end
@@ -19,6 +19,10 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'rvc/vim'
22
+ begin
23
+ require 'net/ssh'
24
+ rescue LoadError
25
+ end
22
26
 
23
27
  opts :reboot do
24
28
  summary "Reboot hosts"
@@ -49,6 +53,37 @@ def reboot hosts, opts
49
53
  end
50
54
  end
51
55
 
56
+ opts :restart_services do
57
+ summary "Restart all services in hosts"
58
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
59
+ opt :password, "Host Password", :default => ''
60
+ end
61
+
62
+ def restart_services hosts, opts
63
+
64
+ hosts.each do |host|
65
+ Net::SSH.start(host.name, "root", :password => opts[:password], :paranoid => false) do |ssh|
66
+ cmd = "/sbin/chkconfig usbarbitrator off"
67
+ puts "Running #{cmd}"
68
+ out = ssh_exec!(ssh,cmd)
69
+ if out[2] != 0
70
+ puts "Failed to execute #{cmd} on host #{host.name}"
71
+ puts out[1]
72
+ end
73
+
74
+ cmd = "/sbin/services.sh restart > /tmp/restart_services.log 2>&1"
75
+ puts "Running #{cmd}"
76
+ out = ssh_exec!(ssh,cmd)
77
+ if out[2] != 0
78
+ puts "Failed to restart all services on host #{host.name}"
79
+ puts out[1]
80
+ else
81
+ puts "Host #{host.name} restarted all services"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
52
87
 
53
88
  opts :evacuate do
54
89
  summary "vMotion all VMs away from this host (experimental)"
@@ -154,6 +189,7 @@ opts :add_iscsi_target do
154
189
  arg :host, nil, :lookup => VIM::HostSystem, :multi => true
155
190
  opt :address, "Address of iSCSI server", :short => 'a', :type => :string, :required => true
156
191
  opt :iqn, "IQN of iSCSI target", :short => 'i', :type => :string, :required => true
192
+ opt :dynamic_target, "Use Dynamic target Discovery", :short => 'd', :type => :boolean
157
193
  end
158
194
 
159
195
  def add_iscsi_target hosts, opts
@@ -162,10 +198,27 @@ def add_iscsi_target hosts, opts
162
198
  storage = host.configManager.storageSystem
163
199
  storage.UpdateSoftwareInternetScsiEnabled(:enabled => true)
164
200
  adapter = storage.storageDeviceInfo.hostBusAdapter.grep(VIM::HostInternetScsiHba)[0]
165
- storage.AddInternetScsiStaticTargets(
166
- :iScsiHbaDevice => adapter.device,
167
- :targets => [ VIM::HostInternetScsiHbaStaticTarget(:address => opts[:address], :iScsiName => opts[:iqn]) ]
168
- )
201
+ if opts[:dynamic_target]
202
+ storage.UpdateInternetScsiName(
203
+ :iScsiHbaDevice => adapter.device,
204
+ :iScsiName => opts[:iqn]
205
+ )
206
+ storage.AddInternetScsiSendTargets(
207
+ :iScsiHbaDevice => adapter.device,
208
+ :targets => [
209
+ VIM::HostInternetScsiHbaSendTarget(:address => opts[:address])
210
+ ]
211
+ )
212
+ else
213
+ storage.AddInternetScsiStaticTargets(
214
+ :iScsiHbaDevice => adapter.device,
215
+ :targets => [
216
+ VIM::HostInternetScsiHbaStaticTarget(
217
+ :address => opts[:address],
218
+ :iScsiName => opts[:iqn])
219
+ ]
220
+ )
221
+ end
169
222
  storage.RescanAllHba
170
223
  end
171
224
  end
@@ -203,3 +256,62 @@ def rescan_storage hosts
203
256
  storageSystem.RescanVmfs
204
257
  end
205
258
  end
259
+
260
+
261
+ opts :select_vmknic_for_service do
262
+ summary "Selects a vmknic for a particular service"
263
+ arg :vmknic, "Name of vmknic", :type => :string
264
+ arg :service, "e.g.: vmotion", :type => :string
265
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
266
+ end
267
+
268
+ def select_vmknic_for_service vmknic, service, hosts
269
+ hosts.each do |host|
270
+ vnicSys = host.configManager.virtualNicManager
271
+ vnicSys.SelectVnicForNicType(:nicType => service, :device => vmknic)
272
+ end
273
+ end
274
+
275
+
276
+ opts :deselect_vmknic_for_service do
277
+ summary "Selects a vmknic for a particular service"
278
+ arg :vmknic, "Name of vmknic", :type => :string
279
+ arg :service, "e.g.: vmotion", :type => :string
280
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
281
+ end
282
+
283
+ def deselect_vmknic_for_service vmknic, service, hosts
284
+ hosts.each do |host|
285
+ vnicSys = host.configManager.virtualNicManager
286
+ vnicSys.DeselectVnicForNicType(:nicType => service, :device => vmknic)
287
+ end
288
+ end
289
+
290
+ # http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library
291
+ def ssh_exec!(ssh, command)
292
+ stdout_data = ""
293
+ stderr_data = ""
294
+ exit_code = nil
295
+ exit_signal = nil
296
+ ssh.open_channel do |channel|
297
+ channel.exec(command) do |ch, success|
298
+ unless success
299
+ abort "FAILED: couldn't execute command (ssh.channel.exec)"
300
+ end
301
+ channel.on_data do |ch,data|
302
+ stdout_data+=data
303
+ end
304
+
305
+ channel.on_extended_data do |ch,type,data|
306
+ stderr_data+=data
307
+ end
308
+
309
+ channel.on_request("exit-status") do |ch,data|
310
+ exit_code = data.read_long
311
+ end
312
+
313
+ end
314
+ end
315
+ ssh.loop
316
+ [stdout_data, stderr_data, exit_code]
317
+ end
@@ -244,14 +244,16 @@ def counters obj
244
244
  groups = available_counters.group_by { |counter| counter.groupInfo }
245
245
 
246
246
  table = Terminal::Table.new
247
- table.add_row ["Name", "Description", "Unit", "Level", "Active Intervals"]
247
+ table.add_row ["Name", "Description", "Unit", "Level", "Per-Dev", "Active Intervals"]
248
248
  groups.sort_by { |group,counters| group.key }.each do |group,counters|
249
249
  table.add_separator
250
250
  table.add_row [{ :value => group.label, :colspan => 5}]
251
251
  table.add_separator
252
252
  counters.sort_by(&:name).each do |counter|
253
+ pp counter
253
254
  table.add_row [counter.name, counter.nameInfo.label, counter.unitInfo.label,
254
- counter.level, active_intervals_text[counter.level]]
255
+ counter.level, counter.perDeviceLevel,
256
+ active_intervals_text[counter.level]]
255
257
  end
256
258
  end
257
259
  puts(table)
@@ -305,11 +307,41 @@ def counter counter_name, obj
305
307
  end
306
308
  end
307
309
 
310
+
311
+ opts :modify_counters do
312
+ summary "Modify perf counter settings"
313
+ arg :metrics, nil, :type => :string, :multi => true
314
+ opt :aggregate_level, nil, :type => :int
315
+ opt :per_device_level, nil, :type => :int
316
+ end
317
+
318
+ def modify_counters counter_names, opts
319
+ vim = lookup_single('~@')
320
+ pm = vim.serviceContent.perfManager
321
+ pm.UpdateCounterLevelMapping(
322
+ :counterLevelMap => counter_names.map do |counter_name|
323
+ counter = pm.perfcounter_hash[counter_name]
324
+ if !counter
325
+ err "no such counter #{counter_name.inspect}"
326
+ end
327
+
328
+ aggregateLevel = opts[:aggregate_level] || counter.level
329
+ perDeviceLevel = opts[:per_device_level] || counter.perDeviceLevel
330
+ {
331
+ :counterId => counter.key,
332
+ :aggregateLevel => aggregateLevel,
333
+ :perDeviceLevel => perDeviceLevel,
334
+ }
335
+ end
336
+ )
337
+ end
338
+
308
339
  opts :stats do
309
340
  summary "Retrieve performance stats for given object"
310
341
  arg :metrics, nil, :type => :string
311
342
  arg :obj, nil, :multi => true, :lookup => VIM::ManagedEntity
312
343
  opt :samples, "Number of samples to retrieve", :type => :int
344
+ opt :instance, "Specify a particular insance instead of *", :type => :string, :multi => true
313
345
  end
314
346
 
315
347
  def stats metrics, objs, opts
@@ -317,6 +349,7 @@ def stats metrics, objs, opts
317
349
 
318
350
  vim = single_connection objs
319
351
  pm = vim.serviceContent.perfManager
352
+ pc = vim.propertyCollector
320
353
 
321
354
  metrics.each do |x|
322
355
  err "no such metric #{x}" unless pm.perfcounter_hash.member? x
@@ -329,21 +362,34 @@ def stats metrics, objs, opts
329
362
  interval = 300
330
363
  start_time = Time.now - 300 * 5
331
364
  end
365
+ instances = opts[:instance] || ['*']
366
+ if instances.length == 0
367
+ instances << '*'
368
+ end
332
369
  stat_opts = {
333
370
  :interval => interval,
334
371
  :startTime => start_time,
372
+ :instance => instances,
373
+ :multi_instance => true,
335
374
  }
336
375
  stat_opts[:max_samples] = opts[:samples] if opts[:samples]
337
376
  res = pm.retrieve_stats objs, metrics, stat_opts
338
377
 
339
378
  table = Terminal::Table.new
340
- table.add_row ['Object', 'Metric', 'Values', 'Unit']
379
+ table.add_row ['Object', 'Instance', 'Metric', 'Values', 'Unit']
341
380
  table.add_separator
342
- objs.each do |obj|
381
+ objs_props = pc.collectMultiple(objs, 'name')
382
+ objs.sort_by{|obj| objs_props[obj]['name']}.each do |obj|
383
+ if !res[obj]
384
+ next
385
+ end
343
386
  metrics.each do |metric|
344
- stat = res[obj][:metrics][metric]
345
- metric_info = pm.perfcounter_hash[metric]
346
- table.add_row([obj.name, metric, stat.join(','), metric_info.unitInfo.label])
387
+ matches = res[obj][:metrics].select{|k, v| k[0] == metric}
388
+ matches.each do |key, stat|
389
+ key, instance = key
390
+ metric_info = pm.perfcounter_hash[metric]
391
+ table.add_row([objs_props[obj]['name'], instance, metric, stat.join(','), metric_info.unitInfo.label])
392
+ end
347
393
  end
348
394
  end
349
395
  puts table
@@ -77,6 +77,7 @@ opts :remove do
77
77
  summary "Remove snapshots"
78
78
  arg :snapshots, nil, :multi => true, :lookup => RVC::SnapshotFolder
79
79
  opt :remove_children, "Whether to remove the snapshot's children too"
80
+ opt :no_consolidate, "Don't consolidate", :type => :boolean
80
81
  end
81
82
 
82
83
  def remove snapshots, opts
@@ -84,6 +85,11 @@ def remove snapshots, opts
84
85
  snapshots.sort_by! {|s| s.rvc_path_str }
85
86
 
86
87
  snapshots.reverse_each do |snapshot|
87
- tasks [snapshot.find_tree.snapshot], :RemoveSnapshot, :removeChildren => opts[:remove_children]
88
+ tasks(
89
+ [snapshot.find_tree.snapshot],
90
+ :RemoveSnapshot,
91
+ :removeChildren => opts[:remove_children],
92
+ :consolidate => !opts[:no_consolidate]
93
+ )
88
94
  end
89
95
  end
@@ -0,0 +1,728 @@
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 'rbvmomi/vim'
22
+ require 'rbvmomi/pbm'
23
+ require 'rvc/vim'
24
+
25
+ PBM = RbVmomi::PBM
26
+
27
+ class RbVmomi::VIM
28
+ def pbm
29
+ @pbm ||= PBM.connect self, :insecure => true
30
+ end
31
+
32
+ def pbm= x
33
+ @pbm = nil
34
+ end
35
+ end
36
+
37
+ module PbmHelperModule
38
+ def _catch_spbm_resets(conn)
39
+ begin
40
+ yield
41
+ rescue EOFError
42
+ if conn
43
+ conn.pbm = nil
44
+ end
45
+ raise "Connection to SPBM timed out, try again"
46
+ end
47
+ end
48
+ end
49
+
50
+ RbVmomi::VIM::Datacenter
51
+ class RbVmomi::VIM::Datacenter
52
+ def rvc_list_children_profiles
53
+ {
54
+ 'storage' => RVC::FakeFolder.new(self, :rvc_children_storage),
55
+ }
56
+ end
57
+
58
+ def rvc_children_storage
59
+ {
60
+ 'vmprofiles' => RVC::FakeFolder.new(self, :rvc_children_profiles),
61
+ }
62
+ end
63
+
64
+ def rvc_children_profiles
65
+ conn = _connection
66
+ _catch_spbm_resets(conn) do
67
+ pbm = conn.pbm
68
+ pm = pbm.serviceContent.profileManager
69
+ profileIds = pm.PbmQueryProfile(
70
+ :resourceType => {:resourceType => "STORAGE"},
71
+ :profileCategory => "REQUIREMENT"
72
+ )
73
+ if profileIds.length > 0
74
+ profiles = pm.PbmRetrieveContent(:profileIds => profileIds)
75
+ else
76
+ profiles = []
77
+ end
78
+
79
+ Hash[profiles.map do |x|
80
+ x.instance_variable_set(:@connection, pbm)
81
+ x.instance_variable_set(:@dc, self)
82
+ [x.name, x]
83
+ end]
84
+ end
85
+ end
86
+ end
87
+
88
+ RbVmomi::PBM::PbmCapabilityInstance
89
+ class RbVmomi::PBM::PbmCapabilityInstance
90
+ def name
91
+ "#{self.id.namespace}.#{self.id.id}"
92
+ end
93
+ end
94
+
95
+
96
+ RbVmomi::PBM::PbmCapabilityMetadata
97
+ class RbVmomi::PBM::PbmCapabilityMetadata
98
+ def name
99
+ "#{self.id.namespace}.#{self.id.id}"
100
+ end
101
+ end
102
+
103
+ RbVmomi::VIM::VirtualDisk
104
+ class RbVmomi::VIM::VirtualDisk
105
+ def rvc_display_info_vsan
106
+ if self.backing.backingObjectId
107
+ puts "VSAN objects:"
108
+ backing = self.backing
109
+ while backing
110
+ puts " #{backing.backingObjectId}"
111
+ backing = backing.parent
112
+ end
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+ RbVmomi::VIM::Datastore
119
+ class RbVmomi::VIM::Datastore
120
+ def to_pbmhub
121
+ PBM::PbmPlacementHub(:hubType => "Datastore", :hubId => _ref)
122
+ end
123
+
124
+ def pbm_capability_profiles
125
+ pbm_associated_profiles
126
+ end
127
+
128
+ def rvc_list_children_capabilitysets
129
+ {
130
+ 'capabilitysets' => RVC::FakeFolder.new(self, :rvc_children_capabilitysets),
131
+ }
132
+ end
133
+
134
+ def rvc_children_capabilitysets
135
+ conn = _connection
136
+ _catch_spbm_resets(conn) do
137
+ pbm = _connection.pbm
138
+ profiles = pbm_capability_profiles
139
+ Hash[profiles.map do |x|
140
+ x.instance_variable_set(:@connection, pbm)
141
+ x.instance_variable_set(:@dc, self)
142
+ [x.name, x]
143
+ end]
144
+ end
145
+ end
146
+ end
147
+
148
+ RbVmomi::VIM::VirtualMachine
149
+ class RbVmomi::VIM::VirtualMachine
150
+ def rvc_list_children_vmprofiles
151
+ {
152
+ 'vmprofiles' => RVC::FakeFolder.new(self, :rvc_children_vmprofiles),
153
+ }
154
+ end
155
+
156
+ def rvc_children_vmprofiles
157
+ conn = _connection
158
+ _catch_spbm_resets(conn) do
159
+ pbm = _connection.pbm
160
+ profiles = pbm_associated_profiles
161
+ Hash[profiles.map do |x|
162
+ x.instance_variable_set(:@connection, pbm)
163
+ x.instance_variable_set(:@dc, self)
164
+ [x.name, x]
165
+ end]
166
+ end
167
+ end
168
+ end
169
+
170
+ RbVmomi::VIM::ManagedObject
171
+ class RbVmomi::VIM::ManagedObject
172
+ def to_pbmobjref
173
+ type = self.class.wsdl_name
174
+ type = "%s%s" % [type[0].downcase, type[1..-1]]
175
+ PBM::PbmServerObjectRef(
176
+ :objectType => type,
177
+ :key => _ref,
178
+ :serverUuid => _connection.serviceContent.about.instanceUuid
179
+ )
180
+ end
181
+
182
+ def pbm_associated_profiles
183
+ conn = _connection
184
+ _catch_spbm_resets(conn) do
185
+ pbm = _connection.pbm
186
+ pm = pbm.serviceContent.profileManager
187
+ ids = pm.PbmQueryAssociatedProfile(:entity => self.to_pbmobjref)
188
+ pm.PbmRetrieveContent(:profileIds => ids)
189
+ end
190
+ end
191
+
192
+ def _catch_spbm_resets(conn)
193
+ begin
194
+ yield
195
+ rescue EOFError
196
+ if conn
197
+ conn.pbm = nil
198
+ end
199
+ raise "Connection to SPBM timed out, try again"
200
+ end
201
+ end
202
+ end
203
+
204
+ RbVmomi::VIM::VirtualMachine
205
+ class RbVmomi::VIM::VirtualMachine
206
+ def disks_pbmobjref(opts = {})
207
+ if opts[:vms_props] && opts[:vms_props][self] &&
208
+ opts[:vms_props][self]['config.hardware.device']
209
+ devices = opts[:vms_props][self]['config.hardware.device']
210
+ _disks = devices.select{|x| x.is_a?(VIM::VirtualDisk)}
211
+ else
212
+ _disks = disks
213
+ end
214
+ _disks.map do |disk|
215
+ PBM::PbmServerObjectRef(
216
+ :objectType => "virtualDiskId",
217
+ :key => "#{self._ref}:#{disk.key}",
218
+ :serverUuid => _connection.serviceContent.about.instanceUuid
219
+ )
220
+ end
221
+ end
222
+
223
+ def all_pbmobjref(opts = {})
224
+ [to_pbmobjref] + disks_pbmobjref(opts)
225
+ end
226
+ end
227
+
228
+ RbVmomi::PBM::PbmPlacementSolver
229
+ class RbVmomi::PBM::PbmPlacementSolver
230
+ def find_compatible_datastores datastores, profileIds
231
+ if profileIds.length > 1
232
+ raise Exception("Passing in more than one profile currently not supported")
233
+ end
234
+ dsMoMap = Hash[datastores.map{|x| [x._ref, x]}]
235
+ results = self.PbmSolve(
236
+ :hubsToSearch => datastores.map{|x| x.to_pbmhub},
237
+ :requirements => [
238
+ {
239
+ :subject => PBM.PbmPlacementSubject(
240
+ :subjectType=>"VirtualMachine",
241
+ :subjectId=>"fake"
242
+ ),
243
+ :requirement => [
244
+ PBM::PbmPlacementCapabilityProfileRequirement(
245
+ :requirementType => "type",
246
+ :mandatory => true,
247
+ :profileId => profileIds[0]
248
+ )
249
+ ],
250
+ }
251
+ ],
252
+ :partialSolution => false
253
+ )
254
+ compatibleDsList = results.map do |x|
255
+ dsMoMap[x.subjectAssignment[0].hub.hubId]
256
+ end
257
+ end
258
+ end
259
+
260
+ RbVmomi::PBM::PbmCapabilityProfile
261
+ class RbVmomi::PBM::PbmCapabilityProfile
262
+ include InventoryObject
263
+ include PbmHelperModule
264
+
265
+ def children
266
+ {
267
+ 'datastores' => RVC::FakeFolder.new(self, :rvc_children_datastores),
268
+ 'vms' => RVC::FakeFolder.new(self, :rvc_children_vms),
269
+ }
270
+ end
271
+
272
+ def rvc_children_vms
273
+ pbm = @connection
274
+ vim = @dc._connection
275
+ pc = vim.propertyCollector
276
+ pm = pbm.serviceContent.profileManager
277
+
278
+ vms = pm.PbmQueryAssociatedEntity(
279
+ :profile => self.profileId,
280
+ :entityType => 'virtualMachine'
281
+ )
282
+ vms = vms.map do |ref|
283
+ VIM::VirtualMachine(vim, ref.key)
284
+ end
285
+ props = pc.collectMultiple(vms, 'name')
286
+ Hash[props.map do |vm, vm_props|
287
+ [vm_props['name'], vm]
288
+ end]
289
+ end
290
+
291
+ def rvc_children_datastores
292
+ pbm = @connection
293
+ vim = @dc._connection
294
+ pc = vim.propertyCollector
295
+ _catch_spbm_resets(vim) do
296
+ solver = pbm.serviceContent.placementSolver
297
+ datastores = solver.find_compatible_datastores @dc.datastore, [profileId]
298
+ props = pc.collectMultiple(datastores, 'name')
299
+ Hash[props.map do |ds, ds_props|
300
+ [ds_props['name'], ds]
301
+ end]
302
+ end
303
+ end
304
+
305
+ def display_info
306
+ super
307
+ puts "Name: #{name}"
308
+ puts "Description:"
309
+ puts description
310
+ puts "ProfileId: #{profileId.uniqueId}"
311
+ puts "Type: #{resourceType.resourceType} - #{profileCategory}"
312
+ puts "Rule-Sets:"
313
+ constraints.subProfiles.each_with_index do |sub, i|
314
+ puts " Rule-Set ##{i + 1}:"
315
+ sub.capability.each do |rule|
316
+ instances = rule.constraint.map{|c| c.propertyInstance}.flatten
317
+ if instances.length > 1
318
+ raise "Can't deal with multiple constraints in single rule"
319
+ end
320
+ value = instances[0].value
321
+ if value.is_a?(RbVmomi::PBM::PbmCapabilityRange)
322
+ value = "#{value.min} - #{value.max}"
323
+ end
324
+ puts " #{rule.name}: #{value}"
325
+ end
326
+ end
327
+ end
328
+ end
329
+
330
+
331
+ opts :profile_delete do
332
+ summary "Delete a VM Storage Profile"
333
+ arg :profile, nil, :lookup => RbVmomi::PBM::PbmCapabilityProfile, :multi => true
334
+ end
335
+
336
+ def profile_delete profiles
337
+ if profiles.length == 0
338
+ return
339
+ end
340
+ _catch_spbm_resets(nil) do
341
+ pbm = profiles.first.instance_variable_get(:@connection)
342
+ pm = pbm.serviceContent.profileManager
343
+ pm.PbmDelete(:profileId => profiles.map{|x| x.profileId})
344
+ end
345
+ end
346
+
347
+
348
+ opts :profile_apply do
349
+ summary "Apply a VM Storage Profile. Pushed profile content to Storage system"
350
+ arg :profile, nil, :lookup => RbVmomi::PBM::PbmCapabilityProfile, :multi => true
351
+ end
352
+
353
+ def profile_apply profiles
354
+ if profiles.length == 0
355
+ return
356
+ end
357
+ pbm = profiles.first.instance_variable_get(:@connection)
358
+ dc = profiles.first.instance_variable_get(:@dc)
359
+ vim = dc._connection
360
+ _catch_spbm_resets(vim) do
361
+ pm = pbm.serviceContent.profileManager
362
+ results = pm.applyProfile(:profiles => profiles.map{|x| x.profileId})
363
+ tasks = results.map{|x| x.reconfigOutcome.map{|y| y.taskMoid}}.flatten
364
+ tasks = tasks.map{|x| VIM::Task(vim, x)}
365
+ progress(tasks)
366
+ end
367
+ end
368
+
369
+
370
+ opts :profile_create do
371
+ summary "Create a VM Storage Profile"
372
+ arg :name, nil, :type => :string
373
+ opt :description, "Description", :type => :string
374
+ opt :rule, "Rule in format <provider>.<capability>=<value>", :type => :string, :multi => true
375
+ end
376
+
377
+ def profile_create profile_name, opts
378
+ dc, = lookup '~'
379
+ conn = dc._connection
380
+ _catch_spbm_resets(conn) do
381
+ pbm = conn.pbm
382
+ pm = pbm.serviceContent.profileManager
383
+
384
+ rules = opts[:rule] || []
385
+ resType = {:resourceType => "STORAGE"}
386
+
387
+ # Need to support other vendors too
388
+ cm = pm.PbmFetchCapabilityMetadata(
389
+ :resourceType => resType,
390
+ :vendorUuid => "com.vmware.storage.vsan"
391
+ )
392
+ capabilities = cm.map{|x| x.capabilityMetadata}.flatten
393
+
394
+ constraints = rules.map do |rule_str|
395
+ name, values_str = rule_str.split("=", 2)
396
+ if !values_str
397
+ err "Rule is malformed: #{rule_str}, should be <provider>.<capability>=<value>"
398
+ end
399
+ ns, id = name.split('.', 2)
400
+ if !id
401
+ err "Rule is malformed: #{rule_str}, should be <provider>.<capability>=<value>"
402
+ end
403
+ capability = capabilities.find{|x| x.name == name}
404
+ if !capability
405
+ err "Capability #{name} unknown"
406
+ end
407
+ type = capability.propertyMetadata[0].type
408
+ values = values_str.split(',')
409
+ if type.typeName == "XSD_INT"
410
+ values = values.map{|x| RbVmomi::BasicTypes::Int.new(x.to_i)}
411
+ end
412
+ if type.typeName == "XSD_BOOLEAN"
413
+ values = values.map{|x| (x =~ /(true|True|1|yes|Yes)/) != nil}
414
+ end
415
+ if type.is_a?(PBM::PbmCapabilityGenericTypeInfo) && type.genericTypeName == "VMW_RANGE"
416
+ if values.length != 2
417
+ err "#{name} is a range, need to specify 2 values"
418
+ end
419
+ value = PBM::PbmCapabilityTypesRange(:min => values[0], :max => values[1])
420
+ elsif values.length == 1
421
+ value = values.first
422
+ else
423
+ err "Value malformed: #{value_str}"
424
+ end
425
+
426
+ {
427
+ :id => {
428
+ :namespace => ns,
429
+ :id => id
430
+ },
431
+ :constraint => [{
432
+ :propertyInstance => [{
433
+ :id => id,
434
+ :value => value
435
+ }]
436
+ }]
437
+ }
438
+ end
439
+ pm.PbmCreate(
440
+ :createSpec => {
441
+ :name => profile_name,
442
+ :description => opts[:description],
443
+ :resourceType => resType,
444
+ :constraints => PBM::PbmCapabilitySubProfileConstraints(
445
+ :subProfiles => [
446
+ PBM::PbmCapabilitySubProfile(
447
+ :name => "Object",
448
+ :capability => constraints
449
+ )
450
+ ]
451
+ )
452
+ }
453
+ )
454
+ end
455
+ end
456
+
457
+ opts :device_change_storage_profile do
458
+ summary "Change storage profile of a virtual disk"
459
+ arg :device, nil, :lookup => VIM::VirtualDevice, :multi => true
460
+ opt :profile, "Profile", :lookup => RbVmomi::PBM::PbmCapabilityProfile
461
+ end
462
+
463
+ def device_change_storage_profile devs, opts
464
+ if !opts[:profile]
465
+ err "Must specify a storage profile"
466
+ end
467
+
468
+ vm_devs = devs.group_by(&:rvc_vm)
469
+ conn = vm_devs.keys.first._connection
470
+ _catch_spbm_resets(conn) do
471
+ _run_with_rev(conn, "dev") do
472
+ profile = nil
473
+ if opts[:profile]
474
+ profile = [VIM::VirtualMachineDefinedProfileSpec(
475
+ :profileId => opts[:profile].profileId.uniqueId
476
+ )]
477
+ end
478
+ tasks = vm_devs.map do |vm, my_devs|
479
+ spec = {
480
+ :deviceChange => my_devs.map do |dev|
481
+ {
482
+ :operation => :edit,
483
+ :device => dev,
484
+ :profile => profile,
485
+ }
486
+ end
487
+ }
488
+ vm.ReconfigVM_Task(:spec => spec)
489
+ end
490
+ progress(tasks)
491
+ end
492
+ end
493
+ end
494
+
495
+ opts :check_compliance do
496
+ summary "Check compliance"
497
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
498
+ end
499
+
500
+ def check_compliance vms
501
+ dc, = lookup '~'
502
+ conn = dc._connection
503
+ _catch_spbm_resets(conn) do
504
+ pbm = conn.pbm
505
+ pm = pbm.serviceContent.profileManager
506
+ cm = pbm.serviceContent.complianceManager
507
+
508
+ compliance = cm.PbmCheckCompliance(:entities => vms.map do |vm|
509
+ vm.all_pbmobjref
510
+ end.flatten)
511
+ profile_ids = Hash[compliance.map{|x| [x.entity.key, x.profile.uniqueId]}]
512
+ compliances = Hash[compliance.map{|x| [x.entity.key, x.complianceStatus]}]
513
+
514
+ profiles = nil
515
+ begin
516
+ profileIds = profile_ids.values.uniq.compact.map do |x|
517
+ PBM::PbmProfileId(:uniqueId => x)
518
+ end
519
+ if profileIds.length > 0
520
+ profiles = pm.PbmRetrieveContent(
521
+ :profileIds => profileIds
522
+ )
523
+ else
524
+ profiles = []
525
+ end
526
+ rescue Exception => ex
527
+ pp "#{ex.class}: #{ex.message}"
528
+ pp profile_ids
529
+ raise ex
530
+ end
531
+ profiles = Hash[profiles.map{|x| [x.profileId.uniqueId, x.name]}]
532
+ profiles = Hash[profile_ids.map{|k,v| [k, profiles[v] || v]}]
533
+
534
+ t = Terminal::Table.new()
535
+ t << ['VM/Virtual Disk', 'Profile', 'Compliance']
536
+ t.add_separator
537
+ vms.each do |vm|
538
+ t << [
539
+ vm.name,
540
+ profiles[vm._ref] || "unknown",
541
+ compliances[vm._ref] || "unknown",
542
+ ]
543
+ vm.disks.each do |disk|
544
+ id = "#{vm._ref}:#{disk.key}"
545
+ t << [
546
+ " #{disk.deviceInfo.label}",
547
+ profiles[id] || "unknown",
548
+ compliances[id] || "unknown",
549
+ ]
550
+ end
551
+ end
552
+ puts t
553
+
554
+ puts ""
555
+ stats = Hash[compliances.values.group_by{|x| x}.map{|k,v| [k, v.length]}]
556
+ stats.sort_by{|k,v| k}.each do |type, count|
557
+ puts "Number of '#{type}' entities: #{count}"
558
+ end
559
+ end
560
+ end
561
+
562
+ opts :namespace_change_storage_profile do
563
+ summary "Change storage profile of VM namespace"
564
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
565
+ opt :profile, "Profile", :lookup => RbVmomi::PBM::PbmCapabilityProfile
566
+ end
567
+
568
+ def namespace_change_storage_profile vms, opts
569
+ if !opts[:profile]
570
+ err "Must specify a storage profile"
571
+ end
572
+
573
+ conn = vms.first._connection
574
+ _catch_spbm_resets(conn) do
575
+ _run_with_rev(conn, "dev") do
576
+ profile = nil
577
+ if opts[:profile]
578
+ profile = [VIM::VirtualMachineDefinedProfileSpec(
579
+ :profileId => opts[:profile].profileId.uniqueId
580
+ )]
581
+ end
582
+ tasks = vms.map do |vm|
583
+ spec = {
584
+ :vmProfile => profile,
585
+ }
586
+ vm.ReconfigVM_Task(:spec => spec)
587
+ end
588
+ progress(tasks)
589
+ end
590
+ end
591
+ end
592
+
593
+ opts :vm_change_storage_profile do
594
+ summary "Change storage profile of VM namespace and its disks"
595
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
596
+ opt :profile, "Profile", :lookup => RbVmomi::PBM::PbmCapabilityProfile
597
+ end
598
+
599
+ def vm_change_storage_profile vms, opts
600
+ if !opts[:profile]
601
+ err "Must specify a storage profile"
602
+ end
603
+
604
+ conn = vms.first._connection
605
+ _catch_spbm_resets(conn) do
606
+ _run_with_rev(conn, "dev") do
607
+ profile = nil
608
+ if opts[:profile]
609
+ profile = [VIM::VirtualMachineDefinedProfileSpec(
610
+ :profileId => opts[:profile].profileId.uniqueId
611
+ )]
612
+ end
613
+ tasks = vms.map do |vm|
614
+ disks = vm.disks
615
+ spec = {
616
+ :vmProfile => profile,
617
+ :deviceChange => disks.map do |dev|
618
+ {
619
+ :operation => :edit,
620
+ :device => dev,
621
+ :profile => profile,
622
+ }
623
+ end
624
+ }
625
+ vm.ReconfigVM_Task(:spec => spec)
626
+ end
627
+ progress(tasks)
628
+ end
629
+ end
630
+ end
631
+
632
+ opts :device_add_disk do
633
+ summary "Add a hard drive to a virtual machine"
634
+ arg :vm, nil, :lookup => VIM::VirtualMachine
635
+ arg :path, "Filename on the datastore", :lookup_parent => VIM::Datastore::FakeDatastoreFolder, :required => false
636
+ opt :size, 'Size', :default => '10G'
637
+ opt :controller, 'Virtual controller', :type => :string, :lookup => VIM::VirtualController
638
+ opt :file_op, 'File operation (create|reuse|replace)', :default => 'create'
639
+ opt :profile, "Profile", :lookup => RbVmomi::PBM::PbmCapabilityProfile
640
+ end
641
+
642
+ def device_add_disk vm, path, opts
643
+ controller, unit_number = pick_controller vm, opts[:controller], [VIM::VirtualSCSIController, VIM::VirtualIDEController]
644
+ id = "disk-#{controller.key}-#{unit_number}"
645
+
646
+ if path
647
+ dir, file = *path
648
+ filename = "#{dir.datastore_path}/#{file}"
649
+ else
650
+ filename = "#{File.dirname(vm.summary.config.vmPathName)}/#{id}.vmdk"
651
+ end
652
+
653
+ opts[:file_op] = nil if opts[:file_op] == 'reuse'
654
+
655
+ conn = vm._connection
656
+ _run_with_rev(conn, "dev") do
657
+ profile = nil
658
+ if opts[:profile]
659
+ profile = [VIM::VirtualMachineDefinedProfileSpec(
660
+ :profileId => opts[:profile].profileId.uniqueId
661
+ )]
662
+ end
663
+ spec = {
664
+ :deviceChange => [
665
+ {
666
+ :operation => :add,
667
+ :fileOperation => opts[:file_op],
668
+ :device => VIM::VirtualDisk(
669
+ :key => -1,
670
+ :backing => VIM.VirtualDiskFlatVer2BackingInfo(
671
+ :fileName => filename,
672
+ :diskMode => :persistent,
673
+ :thinProvisioned => true
674
+ ),
675
+ :capacityInKB => MetricNumber.parse(opts[:size]).to_i/1000,
676
+ :controllerKey => controller.key,
677
+ :unitNumber => unit_number
678
+ ),
679
+ :profile => profile,
680
+ },
681
+ ]
682
+ }
683
+ task = vm.ReconfigVM_Task(:spec => spec)
684
+ result = progress([task])[task]
685
+ if result == nil
686
+ new_device = vm.collect('config.hardware.device')[0].grep(VIM::VirtualDisk).last
687
+ puts "Added device #{new_device.name}"
688
+ end
689
+ end
690
+ end
691
+
692
+
693
+ def pick_controller vm, controller, controller_classes
694
+ existing_devices, = vm.collect 'config.hardware.device'
695
+
696
+ controller ||= existing_devices.find do |dev|
697
+ controller_classes.any? { |klass| dev.is_a? klass } &&
698
+ dev.device.length < 2
699
+ end
700
+ err "no suitable controller found" unless controller
701
+
702
+ used_unit_numbers = existing_devices.select { |dev| dev.controllerKey == controller.key }.map(&:unitNumber)
703
+ unit_number = (used_unit_numbers.max||-1) + 1
704
+
705
+ [controller, unit_number]
706
+ end
707
+
708
+
709
+ def _run_with_rev conn, rev
710
+ old_rev = conn.rev
711
+ begin
712
+ conn.rev = rev
713
+ yield
714
+ ensure
715
+ conn.rev = old_rev
716
+ end
717
+ end
718
+
719
+ def _catch_spbm_resets(conn)
720
+ begin
721
+ yield
722
+ rescue EOFError
723
+ if conn
724
+ conn.pbm = nil
725
+ end
726
+ err "Connection to SPBM timed out, try again"
727
+ end
728
+ end