rbvmomi2 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +114 -0
  4. data/exe/rbvmomish +138 -0
  5. data/lib/rbvmomi/basic_types.rb +383 -0
  6. data/lib/rbvmomi/connection.rb +272 -0
  7. data/lib/rbvmomi/deserialization.rb +249 -0
  8. data/lib/rbvmomi/fault.rb +19 -0
  9. data/lib/rbvmomi/optimist.rb +72 -0
  10. data/lib/rbvmomi/pbm.rb +68 -0
  11. data/lib/rbvmomi/sms/SmsStorageManager.rb +10 -0
  12. data/lib/rbvmomi/sms.rb +63 -0
  13. data/lib/rbvmomi/sso.rb +313 -0
  14. data/lib/rbvmomi/trivial_soap.rb +122 -0
  15. data/lib/rbvmomi/type_loader.rb +138 -0
  16. data/lib/rbvmomi/utils/admission_control.rb +401 -0
  17. data/lib/rbvmomi/utils/deploy.rb +318 -0
  18. data/lib/rbvmomi/utils/leases.rb +145 -0
  19. data/lib/rbvmomi/utils/perfdump.rb +631 -0
  20. data/lib/rbvmomi/version.rb +6 -0
  21. data/lib/rbvmomi/vim/ComputeResource.rb +54 -0
  22. data/lib/rbvmomi/vim/Datacenter.rb +25 -0
  23. data/lib/rbvmomi/vim/Datastore.rb +72 -0
  24. data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +78 -0
  25. data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +23 -0
  26. data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +54 -0
  27. data/lib/rbvmomi/vim/Folder.rb +214 -0
  28. data/lib/rbvmomi/vim/HostSystem.rb +177 -0
  29. data/lib/rbvmomi/vim/ManagedEntity.rb +60 -0
  30. data/lib/rbvmomi/vim/ManagedObject.rb +63 -0
  31. data/lib/rbvmomi/vim/ObjectContent.rb +26 -0
  32. data/lib/rbvmomi/vim/ObjectUpdate.rb +26 -0
  33. data/lib/rbvmomi/vim/OvfManager.rb +204 -0
  34. data/lib/rbvmomi/vim/PerfCounterInfo.rb +28 -0
  35. data/lib/rbvmomi/vim/PerformanceManager.rb +113 -0
  36. data/lib/rbvmomi/vim/PropertyCollector.rb +28 -0
  37. data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +33 -0
  38. data/lib/rbvmomi/vim/ResourcePool.rb +58 -0
  39. data/lib/rbvmomi/vim/ServiceInstance.rb +58 -0
  40. data/lib/rbvmomi/vim/Task.rb +68 -0
  41. data/lib/rbvmomi/vim/VirtualMachine.rb +75 -0
  42. data/lib/rbvmomi/vim.rb +157 -0
  43. data/lib/rbvmomi.rb +16 -0
  44. data/lib/rbvmomi2.rb +3 -0
  45. data/vmodl.db +0 -0
  46. metadata +214 -0
@@ -0,0 +1,401 @@
1
+ # Copyright (c) 2012-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+
5
+ # An admission controlled resource scheduler for large scale vSphere deployments
6
+ #
7
+ # While DRS (Dynamic Resource Scheduler) in vSphere handles CPU and Memory
8
+ # allocations within a single vSphere cluster, larger deployments require
9
+ # another layer of scheduling to make the use of multiple clusters transparent.
10
+ # So this class doesn't replace DRS, but in fact works on top of it.
11
+ #
12
+ # The scheduler in this class performs admission control to make sure clusters
13
+ # don't get overloaded. It does so by adding additional metrics to the already
14
+ # existing CPU and Memory reservation system that DRS has. After admission
15
+ # control it also performs very basic initial placement. Note that in-cluster
16
+ # placement and load-balancing is left to DRS. Also note that no cross-cluster
17
+ # load balancing is done.
18
+ #
19
+ # This class uses the concept of a Pod: A set of clusters that share a set of
20
+ # datastores. From a datastore perspective, we are free to place a VM on any
21
+ # host or cluster. So admission control is done at the Pod level first. Pods
22
+ # are automatically dicovered based on lists of clusters and datastores.
23
+ #
24
+ # Admission control covers the following metrics:
25
+ # - Host availability: If no hosts are available within a cluster or pod,
26
+ # admission is denied.
27
+ # - Minimum free space: If a datastore falls below this free space percentage,
28
+ # admission to it will be denied. Admission to a pod is granted as long at
29
+ # least one datastore passes admission control.
30
+ # - Maximum number of VMs: If a Pod exceeds a configured number of powered on
31
+ # VMs, admission is denied. This is a crude but effective catch-all metric
32
+ # in case users didn't set proper individual CPU or Memory reservations or
33
+ # if the scalability limit doesn't originate from CPU or Memory.
34
+ #
35
+ # Placement after admission control:
36
+ # - Cluster selection: A load metric based on a combination of CPU and Memory
37
+ # load is used to always select the "least loaded" cluster. The metric is very
38
+ # crude and only meant to do very rough load balancing. If DRS clusters are
39
+ # large enough, this is good enough in most cases though.
40
+ # - Datastore selection: Right now NO intelligence is implemented here.
41
+ #
42
+ # Usage:
43
+ # Instantiate the class, call make_placement_decision and then use the exposed
44
+ # computer (cluster), resource pool, vm_folder and datastore. Currently once
45
+ # computed, a new updated placement can't be generated.
46
+ class AdmissionControlledResourceScheduler
47
+ attr_reader :rp
48
+
49
+ def initialize vim, opts = {}
50
+ @vim = vim
51
+
52
+ @datacenter = opts[:datacenter]
53
+ @datacenter_path = opts[:datacenter_path]
54
+ @vm_folder = opts[:vm_folder]
55
+ @vm_folder_path = opts[:vm_folder_path]
56
+ @rp_path = opts[:rp_path]
57
+ @computers = opts[:computers]
58
+ @computer_names = opts[:computer_names]
59
+ @datastores = opts[:datastores]
60
+ @datastore_paths = opts[:datastore_paths]
61
+
62
+ @max_vms_per_pod = opts[:max_vms_per_pod]
63
+ @min_ds_free = opts[:min_ds_free]
64
+ @service_docs_url = opts[:service_docs_url]
65
+
66
+ @pc = @vim.serviceContent.propertyCollector
67
+ @root_folder = @vim.serviceContent.rootFolder
68
+
69
+ @logger = opts[:logger]
70
+ end
71
+
72
+ def log x
73
+ if @logger
74
+ @logger.info x
75
+ else
76
+ puts "#{Time.now}: #{x}"
77
+ end
78
+ end
79
+
80
+ # Returns the used VM folder. If not set yet, uses the vm_folder_path to
81
+ # lookup the folder. If it doesn't exist, it is created. Collisions between
82
+ # multiple clients concurrently creating the same folder are handled.
83
+ # @return [RbVmomi::VIM::Folder] The VM folder
84
+ def vm_folder
85
+ retries = 1
86
+ begin
87
+ @vm_folder ||= datacenter.vmFolder.traverse!(@vm_folder_path, RbVmomi::VIM::Folder)
88
+ if !@vm_folder
89
+ fail "VM folder #{@vm_folder_path} not found"
90
+ end
91
+ rescue RbVmomi::Fault => fault
92
+ if !fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
93
+ raise
94
+ else
95
+ retries -= 1
96
+ retry if retries >= 0
97
+ end
98
+ end
99
+ @vm_folder
100
+ end
101
+
102
+ # Returns the used Datacenter. If not set yet, uses the datacenter_path to
103
+ # lookup the datacenter.
104
+ # @return [RbVmomi::VIM::Datacenter] The datacenter
105
+ def datacenter
106
+ if !@datacenter
107
+ @datacenter = @root_folder.traverse(@datacenter_path, RbVmomi::VIM::Datacenter)
108
+ if !@datacenter
109
+ fail "datacenter #{@datacenter_path} not found"
110
+ end
111
+ end
112
+ @datacenter
113
+ end
114
+
115
+ # Returns the candidate datastores. If not set yet, uses the datastore_paths
116
+ # to lookup the datastores under the datacenter.
117
+ # As a side effect, also looks up properties about all the datastores
118
+ # @return [Array] List of RbVmomi::VIM::Datastore
119
+ def datastores
120
+ if !@datastores
121
+ @datastores = @datastore_paths.map do |path|
122
+ ds = datacenter.datastoreFolder.traverse(path, RbVmomi::VIM::Datastore)
123
+ if !ds
124
+ fail "datastore #{path} not found"
125
+ end
126
+ ds
127
+ end
128
+ end
129
+ if !@datastore_props
130
+ @datastore_props = @pc.collectMultiple(@datastores, 'summary', 'name')
131
+ end
132
+ @datastores
133
+ end
134
+
135
+ # Returns the candidate computers (aka clusters). If not set yet, uses the
136
+ # computer_names to look them up.
137
+ # @return [Array] List of [RbVmomi::VIM::ClusterComputeResource, Hash] tuples, where
138
+ # the Hash is a list of stats about the computer
139
+ def computers
140
+ if !@computers
141
+ @computers = @computer_names.map do |name|
142
+ computer = datacenter.find_compute_resource(name)
143
+ [computer, computer.stats]
144
+ end
145
+ end
146
+ @computers
147
+ end
148
+
149
+ # Returns the candidate pods. If not set, automatically computes the pods
150
+ # based on the list of computers (aka clusters) and datastores.
151
+ # @return [Array] List of pods, where a pod is a list of RbVmomi::VIM::ClusterComputeResource
152
+ def pods
153
+ if !@pods
154
+ # A pod is defined as a set of clusters (aka computers) that share the same
155
+ # datastore accessibility. Computing pods is done automatically using simple
156
+ # set theory math.
157
+ computersProps = @pc.collectMultiple(computers.map{|x| x[0]}, 'datastore')
158
+ @pods = computers.map do |computer, stats|
159
+ computersProps[computer]['datastore'] & self.datastores
160
+ end.uniq.map do |ds_list|
161
+ computers.map{|x| x[0]}.select do |computer|
162
+ (computer.datastore & self.datastores) == ds_list
163
+ end
164
+ end
165
+ end
166
+ @pods
167
+ end
168
+
169
+ # Returns all VMs residing with a pod. Doesn't account for templates. Does so
170
+ # very efficiently using a single API query.
171
+ # @return [Hash] Hash of VMs as keys and their properties as values.
172
+ def pod_vms pod
173
+ # This function retrieves all VMs residing inside a pod
174
+ filterSpec = RbVmomi::VIM.PropertyFilterSpec(
175
+ objectSet: pod.map do |computer, stats|
176
+ {
177
+ obj: computer.resourcePool,
178
+ selectSet: [
179
+ RbVmomi::VIM.TraversalSpec(
180
+ name: 'tsFolder',
181
+ type: 'ResourcePool',
182
+ path: 'resourcePool',
183
+ skip: false,
184
+ selectSet: [
185
+ RbVmomi::VIM.SelectionSpec(name: 'tsFolder'),
186
+ RbVmomi::VIM.SelectionSpec(name: 'tsVM'),
187
+ ]
188
+ ),
189
+ RbVmomi::VIM.TraversalSpec(
190
+ name: 'tsVM',
191
+ type: 'ResourcePool',
192
+ path: 'vm',
193
+ skip: false,
194
+ selectSet: [],
195
+ )
196
+ ]
197
+ }
198
+ end,
199
+ propSet: [
200
+ { type: 'ResourcePool', pathSet: ['name'] },
201
+ { type: 'VirtualMachine', pathSet: %w(runtime.powerState) }
202
+ ]
203
+ )
204
+
205
+ result = @vim.propertyCollector.RetrieveProperties(specSet: [filterSpec])
206
+
207
+ out = result.map { |x| [x.obj, Hash[x.propSet.map { |y| [y.name, y.val] }]] }
208
+ out.select{|obj, props| obj.is_a?(RbVmomi::VIM::VirtualMachine)}
209
+ end
210
+
211
+ # Returns all candidate datastores for a given pod.
212
+ # @return [Array] List of RbVmomi::VIM::Datastore
213
+ def pod_datastores pod
214
+ pod.first.datastore & self.datastores
215
+ end
216
+
217
+ # Returns the list of pods that pass admission control. If not set yet, performs
218
+ # admission control to compute the list. If no pods passed the admission
219
+ # control, an exception is thrown.
220
+ # @return [Array] List of pods, where a pod is a list of RbVmomi::VIM::ClusterComputeResource
221
+ def filtered_pods
222
+ # This function applies admission control and returns those pods that have
223
+ # passed admission control. An exception is thrown if access was denied to
224
+ # all pods.
225
+ if !@filtered_pods
226
+ log "Performing admission control:"
227
+ @filtered_pods = self.pods.select do |pod|
228
+ # Gather some statistics about the pod ...
229
+ on_vms = pod_vms(pod).select{|k,v| v['runtime.powerState'] == 'poweredOn'}
230
+ num_pod_vms = on_vms.length
231
+ pod_datastores = self.pod_datastores(pod)
232
+ log "Pod: #{pod.map{|x| x.name}.join(', ')}"
233
+ log " #{num_pod_vms} VMs"
234
+ pod_datastores.each do |ds|
235
+ ds_sum = @datastore_props[ds]['summary']
236
+ @datastore_props[ds]['free_percent'] = ds_sum.freeSpace.to_f * 100 / ds_sum.capacity
237
+ end
238
+ pod_datastores.each do |ds|
239
+ ds_props = @datastore_props[ds]
240
+ ds_name = ds_props['name']
241
+ free = ds_props['free_percent']
242
+ free_gb = ds_props['summary'].freeSpace.to_f / 1024**3
243
+ free_str = "%.2f GB (%.2f%%)" % [free_gb, free]
244
+ log " Datastore #{ds_name}: #{free_str} free"
245
+ end
246
+
247
+ # Admission check: VM limit
248
+ denied = false
249
+ max_vms = @max_vms_per_pod
250
+ if max_vms && max_vms > 0
251
+ if num_pod_vms > max_vms
252
+ err = "VM limit (#{max_vms}) exceeded on this Pod"
253
+ denied = true
254
+ end
255
+ end
256
+
257
+ # Admission check: Free space on datastores
258
+ min_ds_free = @min_ds_free
259
+ if min_ds_free && min_ds_free > 0
260
+ # We need at least one datastore with enough free space
261
+ low_list = pod_datastores.select do |ds|
262
+ @datastore_props[ds]['free_percent'] <= min_ds_free
263
+ end
264
+
265
+ if low_list.length == pod_datastores.length
266
+ dsNames = low_list.map{|ds| @datastore_props[ds]['name']}.join(", ")
267
+ err = "Datastores #{dsNames} below minimum free disk space (#{min_ds_free}%)"
268
+ denied = true
269
+ end
270
+ end
271
+
272
+ # Admission check: Hosts are available
273
+ if !denied
274
+ hosts_available = pod.any? do |computer|
275
+ stats = Hash[self.computers][computer]
276
+ stats[:totalCPU] > 0 && stats[:totalMem] > 0
277
+ end
278
+ if !hosts_available
279
+ err = "No hosts are current available in this pod"
280
+ denied = true
281
+ end
282
+ end
283
+
284
+ if denied
285
+ log " Admission DENIED: #{err}"
286
+ else
287
+ log " Admission granted"
288
+ end
289
+
290
+ !denied
291
+ end
292
+ end
293
+ if @filtered_pods.length == 0
294
+ log "Couldn't find any Pod with enough resources."
295
+ if @service_docs_url
296
+ log "Check #{@service_docs_url} to see which other Pods you may be able to use"
297
+ end
298
+ fail "Admission denied"
299
+ end
300
+ @filtered_pods
301
+ end
302
+
303
+ # Returns the computer (aka cluster) to be used for placement. If not set yet,
304
+ # computs the least loaded cluster (using a metric that combines CPU and Memory
305
+ # load) that passes admission control.
306
+ # @return [RbVmomi::VIM::ClusterComputeResource] Chosen computer (aka cluster)
307
+ def pick_computer placementhint = nil
308
+ if !@computer
309
+ # Out of the pods to which we have been granted access, pick the cluster
310
+ # (aka computer) with the lowest CPU/Mem utilization for load balancing
311
+ available = self.filtered_pods.flatten
312
+ eligible = self.computers.select do |computer,stats|
313
+ available.member?(computer) && stats[:totalCPU] > 0 and stats[:totalMem] > 0
314
+ end
315
+ computer = nil
316
+ if placementhint
317
+ if eligible.length > 0
318
+ computer = eligible.map{|x| x[0]}[placementhint % eligible.length]
319
+ end
320
+ else
321
+ computer, = eligible.min_by do |computer,stats|
322
+ 2**(stats[:usedCPU].to_f/stats[:totalCPU]) + (stats[:usedMem].to_f/stats[:totalMem])
323
+ end
324
+ end
325
+
326
+ if !computer
327
+ fail "No clusters available, should have been prevented by admission control"
328
+ end
329
+ @computer = computer
330
+ end
331
+ @computer
332
+ end
333
+
334
+ # Returns the datastore to be used for placement. If not set yet, picks a
335
+ # datastore without much intelligence, as long as it passes admission control.
336
+ # @return [RbVmomi::VIM::Datastore] Chosen datastore
337
+ def datastore placementHint = nil
338
+ if @datastore
339
+ return @datastore
340
+ end
341
+
342
+ pod_datastores = pick_computer.datastore & datastores
343
+
344
+ eligible = pod_datastores.select do |ds|
345
+ min_ds_free = @min_ds_free
346
+ if min_ds_free && min_ds_free > 0
347
+ ds_sum = @datastore_props[ds]['summary']
348
+ free_percent = ds_sum.freeSpace.to_f * 100 / ds_sum.capacity
349
+ free_percent > min_ds_free
350
+ else
351
+ true
352
+ end
353
+ end
354
+
355
+ if eligible.length == 0
356
+ fail "Couldn't find any eligible datastore. Admission control should have prevented this"
357
+ end
358
+
359
+ if placementHint && placementHint > 0
360
+ @datastore = eligible[placementHint % eligible.length]
361
+ else
362
+ @datastore = eligible.first
363
+ end
364
+ @datastore
365
+ end
366
+
367
+ # Runs the placement algorithm and populates all the various properties as
368
+ # a side effect. Run this first, before using the other functions of this
369
+ # class.
370
+ def make_placement_decision opts = {}
371
+ self.filtered_pods
372
+ self.pick_computer opts[:placementHint]
373
+ log "Selected compute resource: #{@computer.name}"
374
+
375
+ @rp = @computer.resourcePool.traverse(@rp_path)
376
+ if !@rp
377
+ fail "Resource pool #{@rp_path} not found"
378
+ end
379
+ log "Resource pool: #{@rp.pretty_path}"
380
+
381
+ stats = @computer.stats
382
+ if stats[:totalMem] > 0 && stats[:totalCPU] > 0
383
+ cpu_load = "#{(100*stats[:usedCPU])/stats[:totalCPU]}% cpu"
384
+ mem_load = "#{(100*stats[:usedMem])/stats[:totalMem]}% mem"
385
+ log "Cluster utilization: #{cpu_load}, #{mem_load}"
386
+ end
387
+
388
+ user_vms = vm_folder.inventory_flat('VirtualMachine' => %w(name storage)).select do |k, v|
389
+ k.is_a?(RbVmomi::VIM::VirtualMachine)
390
+ end
391
+ numVms = user_vms.length
392
+ unshared = user_vms.map do |vm, info|
393
+ info['storage'].perDatastoreUsage.map{|x| x.unshared}.inject(0, &:+)
394
+ end.inject(0, &:+)
395
+ log "User stats: #{numVms} VMs using %.2fGB of storage" % [unshared.to_f / 1024**3]
396
+
397
+ @placement_hint = opts[:placement_hint] || (rand(100) + 1)
398
+ datastore = self.datastore @placement_hint
399
+ log "Datastore: #{datastore.name}"
400
+ end
401
+ end