mkuzmin-rbvmomi 1.8.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +6 -0
  3. data/LICENSE +19 -0
  4. data/README.rdoc +78 -0
  5. data/Rakefile +45 -0
  6. data/VERSION +1 -0
  7. data/bin/rbvmomish +138 -0
  8. data/devel/analyze-vim-declarations.rb +213 -0
  9. data/devel/analyze-xml.rb +46 -0
  10. data/devel/benchmark.rb +117 -0
  11. data/devel/collisions.rb +18 -0
  12. data/devel/merge-internal-vmodl.rb +59 -0
  13. data/devel/merge-manual-vmodl.rb +32 -0
  14. data/examples/annotate.rb +54 -0
  15. data/examples/cached_ovf_deploy.rb +120 -0
  16. data/examples/clone_vm.rb +84 -0
  17. data/examples/create_vm-1.9.rb +93 -0
  18. data/examples/create_vm.rb +93 -0
  19. data/examples/extraConfig.rb +54 -0
  20. data/examples/lease_tool.rb +102 -0
  21. data/examples/logbundle.rb +63 -0
  22. data/examples/logtail.rb +60 -0
  23. data/examples/nfs_datastore.rb +95 -0
  24. data/examples/power.rb +59 -0
  25. data/examples/readme-1.rb +35 -0
  26. data/examples/readme-2.rb +51 -0
  27. data/examples/run.sh +41 -0
  28. data/examples/screenshot.rb +48 -0
  29. data/examples/vdf.rb +81 -0
  30. data/examples/vm_drs_behavior.rb +76 -0
  31. data/lib/rbvmomi.rb +12 -0
  32. data/lib/rbvmomi/basic_types.rb +375 -0
  33. data/lib/rbvmomi/connection.rb +270 -0
  34. data/lib/rbvmomi/deserialization.rb +248 -0
  35. data/lib/rbvmomi/fault.rb +17 -0
  36. data/lib/rbvmomi/pbm.rb +66 -0
  37. data/lib/rbvmomi/sms.rb +61 -0
  38. data/lib/rbvmomi/sms/SmsStorageManager.rb +7 -0
  39. data/lib/rbvmomi/trivial_soap.rb +114 -0
  40. data/lib/rbvmomi/trollop.rb +70 -0
  41. data/lib/rbvmomi/type_loader.rb +136 -0
  42. data/lib/rbvmomi/utils/admission_control.rb +398 -0
  43. data/lib/rbvmomi/utils/deploy.rb +314 -0
  44. data/lib/rbvmomi/utils/leases.rb +142 -0
  45. data/lib/rbvmomi/utils/perfdump.rb +628 -0
  46. data/lib/rbvmomi/vim.rb +128 -0
  47. data/lib/rbvmomi/vim/ComputeResource.rb +51 -0
  48. data/lib/rbvmomi/vim/Datacenter.rb +17 -0
  49. data/lib/rbvmomi/vim/Datastore.rb +68 -0
  50. data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +75 -0
  51. data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +20 -0
  52. data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +46 -0
  53. data/lib/rbvmomi/vim/Folder.rb +207 -0
  54. data/lib/rbvmomi/vim/HostSystem.rb +174 -0
  55. data/lib/rbvmomi/vim/ManagedEntity.rb +57 -0
  56. data/lib/rbvmomi/vim/ManagedObject.rb +60 -0
  57. data/lib/rbvmomi/vim/ObjectContent.rb +23 -0
  58. data/lib/rbvmomi/vim/ObjectUpdate.rb +23 -0
  59. data/lib/rbvmomi/vim/OvfManager.rb +200 -0
  60. data/lib/rbvmomi/vim/PerfCounterInfo.rb +26 -0
  61. data/lib/rbvmomi/vim/PerformanceManager.rb +110 -0
  62. data/lib/rbvmomi/vim/PropertyCollector.rb +25 -0
  63. data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +30 -0
  64. data/lib/rbvmomi/vim/ResourcePool.rb +55 -0
  65. data/lib/rbvmomi/vim/ServiceInstance.rb +55 -0
  66. data/lib/rbvmomi/vim/Task.rb +65 -0
  67. data/lib/rbvmomi/vim/VirtualMachine.rb +74 -0
  68. data/test/test_deserialization.rb +383 -0
  69. data/test/test_emit_request.rb +128 -0
  70. data/test/test_exceptions.rb +14 -0
  71. data/test/test_helper.rb +14 -0
  72. data/test/test_misc.rb +24 -0
  73. data/test/test_parse_response.rb +69 -0
  74. data/test/test_serialization.rb +311 -0
  75. data/vmodl.db +0 -0
  76. metadata +163 -0
@@ -0,0 +1,142 @@
1
+ require 'yaml'
2
+
3
+ # A class to manage VM leases
4
+ #
5
+ # This class uses YAML encoded VM annotations (config.annotation) to manage a
6
+ # lease system. It helps add such lease info onto new and existing VMs and to
7
+ # find VMs that have expired leases or that are about to have expired leases.
8
+ # The calling code can use those to generate emails with about-to-expire
9
+ # notifications, suspend, power off or destroy VMs that have exceeded their
10
+ # lease, etc.
11
+ class LeaseTool
12
+ # Lists of VM properties the LeaseTool needs to do its job. Can be used to
13
+ # construct larger property collector calls that retrieve more info than just
14
+ # one subsystem needs.
15
+ # @return [Array] List of property names
16
+ def vms_props_list
17
+ ['name', 'config.annotation']
18
+ end
19
+
20
+ # Fetch all VM properties that the LeaseTool needs on all VMs passed in.
21
+ # @param vms [Array] List of VIM::VirtualMachine instances
22
+ # @return [Hash] Hash of VMs as keys and their properties as values
23
+ def get_vms_props vms
24
+ out = {}
25
+ if vms.length > 0
26
+ pc = vms.first._connection.serviceContent.propertyCollector
27
+ out = pc.collectMultiple(vms, 'name', 'config.annotation')
28
+ end
29
+ out
30
+ end
31
+
32
+ # Retrieve the current time as used by the lease tool.
33
+ # @return [Time] Current time as used by the lease tool
34
+ def current_time
35
+ # XXX: Should swith to time provided by VC
36
+ Time.now
37
+ end
38
+
39
+ # Helper function that sets the lease info in a passed in VM config. If there
40
+ # is no annotation, it is added. If there is an annotation, it is updated to
41
+ # include the lease info. Note that if the annotation isn't YAML, it is
42
+ # overwritten.
43
+ # @param vmconfig [Hash] Virtual Machine config spec
44
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
45
+ # @return [Hash] Updated Virtual Machine config spec
46
+ def set_lease_in_vm_config vmconfig, lease_minutes
47
+ annotation = vmconfig[:annotation]
48
+ annotation ||= ""
49
+ note = YAML.load annotation
50
+ if !note.is_a?(Hash)
51
+ note = {}
52
+ end
53
+ lease = current_time + lease_minutes * 60
54
+ note['lease'] = lease
55
+ vmconfig[:annotation] = YAML.dump(note)
56
+ vmconfig
57
+ end
58
+
59
+ # Issue ReconfigVM_Task on the VM to update the lease. User can pass in current
60
+ # annotation, but if not, it is retrieved on demand. A task is returned, i.e.
61
+ # function doesn't wait for completion.
62
+ # @param vm [VIM::VirtualMachine] Virtual Machine instance
63
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
64
+ # @param annotation [String] 'config.annotation' property of the VM. Optional.
65
+ # @return [VIM::Task] VM reconfiguration task
66
+ def set_lease_on_vm_task vm, lease_minutes, annotation = nil
67
+ if !annotation
68
+ annotation = vm.collect 'config.annotation'
69
+ end
70
+ vmconfig = {:annotation => annotation}
71
+ vmconfig = set_lease_in_vm_config vmconfig, lease_minutes
72
+ # XXX: It may be a good idea to cite the VM version here to avoid
73
+ # concurrent writes to the annotation stepping on each others toes
74
+ vm.ReconfigVM_Task(:spec => vmconfig)
75
+ end
76
+
77
+ # Issue ReconfigVM_Task to set the lease on all VMs that currently do not
78
+ # have a lease. All VM reconfigurations are done in parallel and the function
79
+ # waits for all of them to complete
80
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
81
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
82
+ # @option opts [int] :lease_minutes Time to lease expiration from now in minutes
83
+ # @return [Array] List of previously leaseless VMs that now have a lease
84
+ def set_lease_on_leaseless_vms vms, vmprops, opts = {}
85
+ lease_minutes = opts[:lease_minutes]
86
+ if !lease_minutes
87
+ raise "Expected lease_minutes to be specified"
88
+ end
89
+ vms = find_leaseless_vms vms, vmprops
90
+ if vms.length > 0
91
+ tasks = vms.map do |vm|
92
+ annotation = vmprops[vm]['config.annotation']
93
+ task = set_lease_on_vm_task(vm, lease_minutes, annotation)
94
+ task
95
+ end
96
+ si = vms.first._connection.serviceInstance
97
+ si.wait_for_multiple_tasks [], tasks
98
+ end
99
+ vms
100
+ end
101
+
102
+ # Filter the list of passed in Virtual Machines and find the ones that currently
103
+ # do not have a lease.
104
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
105
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
106
+ # @return [Array] List of leaseless VMs
107
+ def find_leaseless_vms vms, vmprops
108
+ vms.reject do |vm|
109
+ props = vmprops[vm]
110
+ annotation = props['config.annotation']
111
+ if annotation
112
+ note = YAML.load annotation
113
+ note.is_a?(Hash) && note['lease']
114
+ end
115
+ end
116
+ end
117
+
118
+ # Filter the list of passed in Virtul Machines and find the one that are
119
+ # expired. A time offset can be used to identify VMs that will expire at
120
+ # a certain point in the future.
121
+ # If a VM doesn't have a lease, it is treated as never expiring.
122
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
123
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
124
+ # @option opts [int] :time_delta Time delta (seconds) to be added to current time
125
+ # @return [Array] List of expired VMs
126
+ def filter_expired_vms vms, vmprops, opts = {}
127
+ time_delta = opts[:time_delta] || 0
128
+ time = current_time + time_delta
129
+
130
+ out = vms.map do |vm|
131
+ props = vmprops[vm]
132
+ next unless annotation = props['config.annotation']
133
+ note = YAML.load annotation
134
+ next unless note.is_a?(Hash) && lease = note['lease']
135
+ next unless time > lease
136
+ time_to_expiration = ((lease - time) + time_delta)
137
+ [vm, time_to_expiration]
138
+ end.compact
139
+ out = Hash[out]
140
+ out
141
+ end
142
+ end
@@ -0,0 +1,628 @@
1
+ require 'set'
2
+ require 'yaml'
3
+
4
+ # PerfAggregator is a class that, given connections to a list of vCenter
5
+ # Servers, will fetch the entire VM folder and ResourcePool hierarchies,
6
+ # including all VIM::VirtualMachine objects and aggregate VM stats along
7
+ # the tree hierarchies. The PerfAggregator class allows for users to
8
+ # perform post processing on the data returned by vCenter, e.g. to augment
9
+ # it with addtional data that was obtained using a combination of
10
+ # VM annotations (or custom values) and an external DB. Post processing
11
+ # can also define additional tree structures that may be completely
12
+ # independent of the VM folder and ResourcePool hirarchies provided by
13
+ # vCenter, e.g. one based on VMs used for testing of a set of source code
14
+ # branches.
15
+ class PerfAggregator
16
+ attr_accessor :path_types
17
+
18
+ def initialize logger = nil
19
+ @logger = logger
20
+ @path_types = Set.new
21
+ @path_types << 'rp'
22
+ @path_types << 'vmfolder'
23
+
24
+ # XXX: Rename this variable
25
+ @perf_metrics = {
26
+ 'virtualDisk.read' => :sum,
27
+ 'virtualDisk.write' => :sum,
28
+ 'virtualDisk.numberReadAveraged' => :sum,
29
+ 'virtualDisk.numberWriteAveraged' => :sum,
30
+ 'virtualDisk.totalReadLatency.avg' => :avg_ignore_zero,
31
+ 'virtualDisk.totalWriteLatency.avg' => :avg_ignore_zero,
32
+ 'virtualDisk.totalReadLatency.max' => :max,
33
+ 'virtualDisk.totalWriteLatency.max' => :max,
34
+ 'num.vm' => :sum,
35
+ 'num.poweredonvm' => :sum,
36
+ 'summary.quickStats.hostMemoryUsage' => :sum,
37
+ 'summary.quickStats.guestMemoryUsage' => :sum,
38
+ 'summary.quickStats.overallCpuUsage' => :sum,
39
+ 'summary.config.memorySizeMB' => :sum,
40
+ 'summary.config.numCpu' => :sum,
41
+ 'storage.space.committed' => :sum,
42
+ 'storage.space.uncommitted' => :sum,
43
+ 'storage.space.unshared' => :sum,
44
+ }
45
+ end
46
+
47
+ def log text
48
+ if @logger
49
+ @logger.info text
50
+ else
51
+ puts "#{Time.now}: #{text}"
52
+ end
53
+ end
54
+
55
+ def set_vm_processing_callback &block
56
+ @vm_processing_callback = block
57
+ end
58
+
59
+ def add_node_unless_exists inventory, id, props
60
+ if !inventory[id]
61
+ inventory[id] = props.merge({'children' => []})
62
+ end
63
+ end
64
+
65
+ # Method that extracts the entire VM folder and ResourcePool hierarchy
66
+ # from vCenter with a single API call. It generates a flat list of
67
+ # VIM objects which will include VIM::Folder, VIM::Datacenter,
68
+ # VIM::ClusterComputeResource, VIM::ResourcePool and VIM::VirtualMachine.
69
+ #
70
+ # Post processing is done (using helper methods) to populate full paths,
71
+ # lists of parents (ancestry) so that the tree structure can be understood.
72
+ # Information about two seperate sub-trees is gathered: The tree following
73
+ # the VM folders and one tree following the clusters and resource pools.
74
+ # In the vSphere Client there are called the "VM/Template View" and the
75
+ # "Host and Clusters View".
76
+ #
77
+ # @param rootFolder [VIM::Folder] Expected to be the rootFolder of the VC
78
+ # @param vm_prop_names [Array] List of VM properties to fetch
79
+ def all_inventory_flat rootFolder, vm_prop_names = ['name']
80
+ conn = rootFolder._connection
81
+ pc = conn.propertyCollector
82
+
83
+ filterSpec = RbVmomi::VIM.PropertyFilterSpec(
84
+ :objectSet => [
85
+ :obj => rootFolder,
86
+ :selectSet => [
87
+ RbVmomi::VIM.TraversalSpec(
88
+ :name => 'tsFolder',
89
+ :type => 'Folder',
90
+ :path => 'childEntity',
91
+ :skip => false,
92
+ :selectSet => [
93
+ RbVmomi::VIM.SelectionSpec(:name => 'tsFolder'),
94
+ RbVmomi::VIM.SelectionSpec(:name => 'tsDatacenterVmFolder'),
95
+ RbVmomi::VIM.SelectionSpec(:name => 'tsDatacenterHostFolder'),
96
+ RbVmomi::VIM.SelectionSpec(:name => 'tsClusterRP'),
97
+ RbVmomi::VIM.SelectionSpec(:name => 'tsClusterHost'),
98
+ ]
99
+ ),
100
+ RbVmomi::VIM.TraversalSpec(
101
+ :name => 'tsDatacenterVmFolder',
102
+ :type => 'Datacenter',
103
+ :path => 'vmFolder',
104
+ :skip => false,
105
+ :selectSet => [
106
+ RbVmomi::VIM.SelectionSpec(:name => 'tsFolder')
107
+ ]
108
+ ),
109
+ RbVmomi::VIM.TraversalSpec(
110
+ :name => 'tsDatacenterHostFolder',
111
+ :type => 'Datacenter',
112
+ :path => 'hostFolder',
113
+ :skip => false,
114
+ :selectSet => [
115
+ RbVmomi::VIM.SelectionSpec(:name => 'tsFolder')
116
+ ]
117
+ ),
118
+ RbVmomi::VIM.TraversalSpec(
119
+ :name => 'tsClusterRP',
120
+ :type => 'ClusterComputeResource',
121
+ :path => 'resourcePool',
122
+ :skip => false,
123
+ :selectSet => [
124
+ RbVmomi::VIM.SelectionSpec(:name => 'tsRP'),
125
+ ]
126
+ ),
127
+ RbVmomi::VIM.TraversalSpec(
128
+ :name => 'tsClusterHost',
129
+ :type => 'ClusterComputeResource',
130
+ :path => 'host',
131
+ :skip => false,
132
+ :selectSet => []
133
+ ),
134
+ RbVmomi::VIM.TraversalSpec(
135
+ :name => 'tsRP',
136
+ :type => 'ResourcePool',
137
+ :path => 'resourcePool',
138
+ :skip => false,
139
+ :selectSet => [
140
+ RbVmomi::VIM.SelectionSpec(:name => 'tsRP'),
141
+ ]
142
+ ),
143
+ ]
144
+ ],
145
+ :propSet => [
146
+ { :type => 'Folder', :pathSet => ['name', 'parent'] },
147
+ { :type => 'Datacenter', :pathSet => ['name', 'parent'] },
148
+ { :type => 'ClusterComputeResource',
149
+ :pathSet => ['name', 'parent', 'summary.effectiveCpu', 'summary.effectiveMemory']
150
+ },
151
+ { :type => 'ResourcePool', :pathSet => ['name', 'parent'] },
152
+ { :type => 'HostSystem', :pathSet => ['name', 'parent', 'runtime.connectionState'] },
153
+ { :type => 'VirtualMachine', :pathSet => vm_prop_names },
154
+ ]
155
+ )
156
+
157
+ result = pc.RetrieveProperties(:specSet => [filterSpec])
158
+ inventory = {}
159
+ vms = {}
160
+ result.each do |r|
161
+ if r.obj.is_a?(RbVmomi::VIM::VirtualMachine)
162
+ vms[r.obj] = r.to_hash
163
+ else
164
+ inventory[r.obj] = r.to_hash
165
+ end
166
+ end
167
+ inventory['root'] = {
168
+ 'name' => 'root',
169
+ 'path' => 'root',
170
+ 'parent' => nil,
171
+ 'parents' => [],
172
+ }
173
+ inventory[conn.host] = {
174
+ 'name' => conn.host,
175
+ 'path' => "root/#{conn.host}",
176
+ 'parent' => 'root',
177
+ 'parents' => ['root'],
178
+ }
179
+ _compute_vmfolders_and_rp_paths conn.host, inventory
180
+ _compute_parents_and_children inventory
181
+ [vms, inventory]
182
+ end
183
+
184
+ # Helper method that computes full paths and parent lists out of a
185
+ # flat list of objects. Operates recursively and doesn't yet split
186
+ # the paths into different tree types.
187
+ # @param obj [Hash] Property hash of current element
188
+ # @param objs [Array] Flat list of tree elements
189
+ def _compute_vmfolder_and_rp_path_and_parents vc, obj, objs
190
+ if obj['path']
191
+ return
192
+ end
193
+ if !obj['parent']
194
+ obj['parent'] = vc
195
+ obj['path'] = "root/#{vc}/#{obj['name']}"
196
+ obj['parents'] = ['root', vc]
197
+ return
198
+ end
199
+ parent = objs[obj['parent']]
200
+ _compute_vmfolder_and_rp_path_and_parents(vc, parent, objs)
201
+ obj['path'] = "%s/%s" % [parent['path'], obj['name']]
202
+ obj['parents'] = [obj['parent']] + parent['parents']
203
+ nil
204
+ end
205
+
206
+ # Helper method that computes full paths and parent lists out of a
207
+ # flat list of objects. Full paths are tracked seperately per type
208
+ # of tree, i.e. seperately for the ResourcePool tree and the VM folder
209
+ # tree.
210
+ # @param objs [Array] Flat list of tree elements
211
+ def _compute_vmfolders_and_rp_paths vc, objs
212
+ objs.each do |obj, props|
213
+ _compute_vmfolder_and_rp_path_and_parents(vc, props, objs)
214
+
215
+ props['paths'] = {}
216
+ obj_with_parents = [obj] + props['parents']
217
+ dc = obj_with_parents.find{|x| x.is_a?(RbVmomi::VIM::Datacenter)}
218
+ # Everything above and including a VIM::Datacenter is part of
219
+ # both the rp and vmfolder tree. Anything below depends on the
220
+ # folder of the datacenter it is under: The hostFolder is called
221
+ # "host" while the root vm folder is called "vm".
222
+ if !dc || obj.is_a?(RbVmomi::VIM::Datacenter)
223
+ props['paths']['rp'] = props['path']
224
+ props['paths']['vmfolder'] = props['path']
225
+ else
226
+ dc_index = obj_with_parents.index dc
227
+ folder = obj_with_parents[dc_index - 1]
228
+ if objs[folder]['name'] == 'host'
229
+ props['paths']['rp'] = props['path']
230
+ else
231
+ props['paths']['vmfolder'] = props['path']
232
+ end
233
+ end
234
+
235
+ props['children'] = []
236
+ end
237
+ end
238
+
239
+ # Helper method that computes children references and parent paths on
240
+ # all objects, if not computed yet. Assumes that full paths of each
241
+ # object have been calculated already.
242
+ # @param objs [Array] Flat list of tree elements
243
+ def _compute_parents_and_children objs
244
+ objs.each do |obj, props|
245
+ if props['parent_paths']
246
+ next
247
+ end
248
+ props['parent_paths'] = {}
249
+ if !props['parent']
250
+ next
251
+ end
252
+ parent = objs[props['parent']]
253
+ props['paths'].keys.each do |type|
254
+ props['parent_paths'][type] = parent['paths'][type]
255
+ end
256
+ parent['children'] << obj
257
+ end
258
+ end
259
+
260
+ def _aggregate_metrics vms_stats, perf_metrics
261
+ out = Hash[perf_metrics.keys.map{|x| [x, 0]}]
262
+ avg_counter = Hash[perf_metrics.keys.map{|x| [x, 0]}]
263
+
264
+ vms_stats.each do |vm_stats|
265
+ perf_metrics.each do |key, type|
266
+ values = vm_stats[key]
267
+ if !values.is_a?(Array)
268
+ values = [values]
269
+ end
270
+ values.compact.each do |val|
271
+ if type == :sum
272
+ out[key] += val
273
+ elsif type == :max
274
+ out[key] = [out[key], val].max
275
+ elsif type == :avg
276
+ out[key] += val.to_f
277
+ avg_counter[key] += 1
278
+ elsif type == :avg_ignore_zero
279
+ if val > 0
280
+ out[key] += val.to_f
281
+ avg_counter[key] += 1
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ perf_metrics.each do |key, type|
289
+ if type == :avg_ignore_zero || type == :avg
290
+ if avg_counter[key] > 0
291
+ out[key] = out[key] / avg_counter[key]
292
+ end
293
+ end
294
+ end
295
+
296
+ out
297
+ end
298
+
299
+ def _collect_info_on_all_vms_single root_folder, opts = {}
300
+ prop_names = opts[:prop_names]
301
+ if !prop_names
302
+ prop_names = [
303
+ 'name',
304
+ 'config.template',
305
+ 'runtime.powerState', 'datastore', 'config.annotation',
306
+ 'parent', 'resourcePool', 'storage.perDatastoreUsage',
307
+ 'summary.config.memorySizeMB',
308
+ 'summary.config.numCpu',
309
+ 'summary.quickStats.hostMemoryUsage',
310
+ 'summary.quickStats.guestMemoryUsage',
311
+ 'summary.quickStats.overallCpuUsage',
312
+ 'runtime.connectionState',
313
+ 'config.instanceUuid',
314
+ 'customValue',
315
+ ]
316
+ end
317
+ perf_metrics = opts[:perf_metrics]
318
+ if !perf_metrics
319
+ perf_metrics = {
320
+ 'virtualDisk.read' => :avg,
321
+ 'virtualDisk.write' => :avg,
322
+ 'virtualDisk.numberReadAveraged' => :avg,
323
+ 'virtualDisk.numberWriteAveraged' => :avg,
324
+ 'virtualDisk.totalReadLatency' => :avg_ignore_zero,
325
+ 'virtualDisk.totalWriteLatency' => :avg_ignore_zero,
326
+ }
327
+ end
328
+ host_perf_metrics = opts[:host_perf_metrics]
329
+ if !host_perf_metrics
330
+ host_perf_metrics = {
331
+ 'cpu.usage' => :avg,
332
+ 'mem.usage' => :avg,
333
+ }
334
+ end
335
+
336
+ vms_props, inventory = all_inventory_flat root_folder, prop_names
337
+ vms = vms_props.keys
338
+
339
+ hosts_props = inventory.select{|k, v| k.is_a?(VIM::HostSystem)}
340
+
341
+ conn = root_folder._connection
342
+ sc = conn.serviceContent
343
+ pc = sc.propertyCollector
344
+ pm = sc.perfManager
345
+ vc_uuid = conn.instanceUuid
346
+
347
+ connected_vms = vms_props.select do |vm, props|
348
+ is_connected = props['runtime.connectionState'] != "disconnected"
349
+ is_template = props['config.template']
350
+ is_connected && !is_template
351
+ end.keys
352
+
353
+ begin
354
+ # XXX: Need to find a good way to get the "right" samples
355
+ if connected_vms.length == 0
356
+ {}
357
+ else
358
+ vms_stats = pm.retrieve_stats(
359
+ connected_vms, perf_metrics.keys,
360
+ :max_samples => 3
361
+ )
362
+ end
363
+ rescue RbVmomi::Fault => ex
364
+ if ex.fault.is_a? RbVmomi::VIM::ManagedObjectNotFound
365
+ connected_vms -= [ex.fault.obj]
366
+ retry
367
+ end
368
+ raise
369
+ end
370
+
371
+ connected_hosts = hosts_props.select do |k,v|
372
+ v['runtime.connectionState'] != "disconnected"
373
+ end
374
+ if connected_hosts.length > 0
375
+ hosts_stats = pm.retrieve_stats(
376
+ connected_hosts.keys, host_perf_metrics.keys,
377
+ :max_samples => 3
378
+ )
379
+ end
380
+ hosts_props.each do |host, props|
381
+ if !connected_hosts[host]
382
+ next
383
+ end
384
+
385
+ stats = hosts_stats[host] || {}
386
+ stats = stats[:metrics] || {}
387
+ stats = _aggregate_metrics [stats], host_perf_metrics
388
+ props.merge!(stats)
389
+ end
390
+
391
+ vms_props.each do |vm, props|
392
+ if !connected_vms.member?(vm)
393
+ next
394
+ end
395
+ props['num.vm'] = 1
396
+ powered_on = (props['runtime.powerState'] == 'poweredOn')
397
+ props['num.poweredonvm'] = powered_on ? 1 : 0
398
+
399
+ stats = vms_stats[vm] || {}
400
+ stats = stats[:metrics] || {}
401
+ stats = _aggregate_metrics [stats], perf_metrics
402
+ props.merge!(stats)
403
+ props['virtualDisk.totalReadLatency.avg'] = props['virtualDisk.totalReadLatency']
404
+ props['virtualDisk.totalWriteLatency.avg'] = props['virtualDisk.totalWriteLatency']
405
+ props['virtualDisk.totalReadLatency.max'] = props['virtualDisk.totalReadLatency']
406
+ props['virtualDisk.totalWriteLatency.max'] = props['virtualDisk.totalWriteLatency']
407
+ props.delete('virtualDisk.totalReadLatency')
408
+ props.delete('virtualDisk.totalWriteLatency')
409
+
410
+ per_ds_usage = props['storage.perDatastoreUsage']
411
+ props['storage.space.committed'] = per_ds_usage.map{|x| x.committed}.inject(0, &:+)
412
+ props['storage.space.uncommitted'] = per_ds_usage.map{|x| x.uncommitted}.inject(0, &:+)
413
+ props['storage.space.unshared'] = per_ds_usage.map{|x| x.unshared}.inject(0, &:+)
414
+
415
+ props['parent_paths'] = {}
416
+ if inventory[props['parent']]
417
+ props['parent_paths']['vmfolder'] = inventory[props['parent']]['path']
418
+ end
419
+ if !props['config.template']
420
+ rp_props = inventory[props['resourcePool']]
421
+ props['parent_paths']['rp'] = rp_props['path']
422
+ end
423
+
424
+ props['annotation_yaml'] = YAML.load(props['config.annotation'] || '')
425
+ if !props['annotation_yaml'].is_a?(Hash)
426
+ props['annotation_yaml'] = {}
427
+ end
428
+
429
+ props['customValue'] = Hash[props['customValue'].map do |x|
430
+ [x.key, x.value]
431
+ end]
432
+
433
+ props['vc_uuid'] = vc_uuid
434
+ end
435
+
436
+ [vms_props, inventory, hosts_props]
437
+ end
438
+
439
+ def collect_info_on_all_vms root_folders, opts = {}
440
+ log "Fetching information from all VCs ..."
441
+ vms_props = {}
442
+ hosts_props = {}
443
+ inventory = {}
444
+ lock = Mutex.new
445
+ root_folders.map do |root_folder|
446
+ Thread.new do
447
+ begin
448
+ single_vms_props, single_inventory, single_hosts_props =
449
+ _collect_info_on_all_vms_single(root_folder, opts)
450
+
451
+ lock.synchronize do
452
+ vms_props.merge!(single_vms_props)
453
+ if inventory['root']
454
+ single_inventory['root']['children'] += inventory['root']['children']
455
+ end
456
+ inventory.merge!(single_inventory)
457
+ hosts_props.merge!(single_hosts_props)
458
+ end
459
+ rescue Exception => ex
460
+ log "#{ex.class}: #{ex.message}"
461
+ ex.backtrace.each do |line|
462
+ log line
463
+ end
464
+ raise
465
+ end
466
+ end
467
+ end.each{|t| t.join}
468
+
469
+ log "Make data marshal friendly ..."
470
+ inventory = _make_marshal_friendly(inventory)
471
+ vms_props = _make_marshal_friendly(vms_props)
472
+ hosts_props = _make_marshal_friendly(hosts_props)
473
+
474
+ log "Perform external post processing ..."
475
+ if @vm_processing_callback
476
+ @vm_processing_callback.call(self, vms_props, inventory)
477
+ end
478
+
479
+ log "Perform data aggregation ..."
480
+ # Processing the annotations may have added new nodes to the
481
+ # inventory list, hence we need to run _compute_parents_and_children
482
+ # again to calculate the parents and children for the newly
483
+ # added nodes.
484
+ _compute_parents_and_children inventory
485
+
486
+ # Now that we have all VMs and a proper inventory tree built, we can
487
+ # aggregate the VM stats along all trees and tree nodes. This
488
+ # de-normalizes the data heavily, but thats fine
489
+ path_types = opts[:path_types] || @path_types
490
+ inventory = _aggregate_vms path_types, vms_props, inventory
491
+
492
+ log "Done collecting and aggregating stats"
493
+
494
+ @inventory = inventory
495
+ @vms_props = vms_props
496
+
497
+ {
498
+ 'inventory' => inventory,
499
+ 'vms_props' => vms_props,
500
+ 'hosts_props' => hosts_props,
501
+ }
502
+ end
503
+
504
+ def _make_marshal_friendly hash
505
+ hash = Hash[hash.map do |k, v|
506
+ if v['parent']
507
+ v['parent'] = _mo2str(v['parent'])
508
+ end
509
+ if v['resourcePool']
510
+ v['resourcePool'] = _mo2str(v['resourcePool'])
511
+ end
512
+ if v['children']
513
+ v['children'] = v['children'].map{|x| _mo2str(x)}
514
+ end
515
+ if v['parents']
516
+ v['parents'] = v['parents'].map{|x| _mo2str(x)}
517
+ end
518
+ if v['datastore']
519
+ v['datastore'] = v['datastore'].map{|x| _mo2str(x)}
520
+ end
521
+ v['type'] = k.class.name
522
+ [_mo2str(k), v]
523
+ end]
524
+ # Marhsal hash to JSON and back. This is just debug code to ensure
525
+ # that all further processing can be done on a serialized dump of
526
+ # the data.
527
+ hash = JSON.load(JSON.dump(hash))
528
+ end
529
+
530
+ def _mo2str mo
531
+ if !mo.is_a?(RbVmomi::VIM::ManagedObject)
532
+ mo
533
+ else
534
+ "vim-#{mo._connection.instanceUuid}-#{mo._ref}"
535
+ end
536
+ end
537
+
538
+ # Helper method that aggregates the VM stats along all trees and
539
+ # tree nodes. This de-normalizes the data heavily, but thats fine.
540
+ def _aggregate_vms path_types, vms_props, inventory
541
+ # XXX: Opimtization:
542
+ # This function is currently quite wasteful. It computes all VMs
543
+ # at each level and then aggregates the VMs for each node individually
544
+ # Instead, the aggregation itself should explot the tree structure.
545
+ path_types.each do |path_type|
546
+ index = {}
547
+ reverse_index = {}
548
+ inventory.each do |k, v|
549
+ if v['paths'] && v['paths'][path_type]
550
+ path = v['paths'][path_type]
551
+ index[path] = v
552
+ reverse_index[path] = k
553
+ end
554
+ end
555
+
556
+ paths_vms = {}
557
+
558
+ vms_props.each do |vm, props|
559
+ if !props['parent_paths'] || !props['parent_paths'][path_type]
560
+ next
561
+ end
562
+ parent_path = props['parent_paths'][path_type]
563
+ while parent_path
564
+ parent = index[parent_path]
565
+ if !parent
566
+ puts "Parent is nil, so dumping some stuff"
567
+ puts path_type
568
+ puts "parent path: #{parent_path}"
569
+ pp index.keys
570
+ pp props
571
+ end
572
+ paths_vms[parent_path] ||= []
573
+ paths_vms[parent_path] << vm
574
+ parent_path = parent['parent_paths'][path_type]
575
+ end
576
+ end
577
+
578
+ paths_vms.each do |k, vms|
579
+ inventory[reverse_index[k]]['vms'] ||= {}
580
+ inventory[reverse_index[k]]['vms'][path_type] = vms
581
+ vms_stats = vms_props.select{|k, v| vms.member?(k)}.values
582
+ stats = _aggregate_metrics vms_stats, @perf_metrics
583
+ inventory[reverse_index[k]]['stats'] ||= {}
584
+ inventory[reverse_index[k]]['stats'][path_type] = stats
585
+ end
586
+
587
+ #pp paths_vms.map{|k, v| [k, reverse_index[k], v.length, index[k]['stats'][path_type].length]}
588
+ end
589
+
590
+ inventory
591
+ end
592
+
593
+ def visualize_vm_props
594
+ path_types_rows = construct_tree_rows_from_vm_props
595
+ path_types_rows.each do |path_type, rows|
596
+ puts "Path type #{path_type}:"
597
+ rows.each do |row|
598
+ indent, name, stats = row
599
+ puts "#{' ' * indent}#{name}: #{stats['num.vm']}"
600
+ end
601
+ puts ""
602
+ end
603
+ end
604
+
605
+ def construct_tree_rows_from_vm_props path_types = nil
606
+ path_types ||= @path_types
607
+ def visualize_node path_type, node, inventory, indent = 0
608
+ rows = []
609
+ if !node || !node['stats'] || !node['stats'][path_type]
610
+ stats = {}
611
+ return []
612
+ else
613
+ stats = node['stats'][path_type]
614
+ end
615
+ rows << [indent, node['name'], stats]
616
+ node['children'].each do |child|
617
+ rows += visualize_node path_type, inventory[child], inventory, indent + 1
618
+ end
619
+ rows
620
+ end
621
+
622
+ Hash[path_types.map do |path_type|
623
+ key, root = @inventory.find{|k, v| v['paths'][path_type] == 'root'}
624
+ rows = visualize_node path_type, root, @inventory
625
+ [path_type, rows]
626
+ end]
627
+ end
628
+ end