rvc 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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