rbvmomi 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -9,8 +9,8 @@ begin
9
9
  gem.summary = "Ruby interface to the VMware vSphere API"
10
10
  #gem.description = ""
11
11
  gem.email = "rlane@vmware.com"
12
- gem.homepage = "https://github.com/rlane/rbvmomi"
13
- gem.authors = ["Rich Lane"]
12
+ gem.homepage = "https://github.com/vmware/rbvmomi"
13
+ gem.authors = ["Rich Lane", "Christian Dickmann"]
14
14
  gem.add_dependency 'nokogiri', '>= 1.4.1'
15
15
  gem.add_dependency 'builder'
16
16
  gem.add_dependency 'trollop'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.1
1
+ 1.6.0
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ require 'trollop'
3
+ require 'rbvmomi'
4
+ require 'rbvmomi/trollop'
5
+ require 'rbvmomi/utils/deploy'
6
+ require 'rbvmomi/utils/admission_control'
7
+ require 'yaml'
8
+
9
+ VIM = RbVmomi::VIM
10
+
11
+ opts = Trollop.options do
12
+ banner <<-EOS
13
+ Deploy an OVF to a cluster, using a cached template if available.
14
+
15
+ Usage:
16
+ cached_ovf_deploy.rb [options] <vmname> <ovfurl>
17
+
18
+ VIM connection options:
19
+ EOS
20
+
21
+ rbvmomi_connection_opts
22
+
23
+ text <<-EOS
24
+
25
+ VM location options:
26
+ EOS
27
+
28
+ rbvmomi_datacenter_opt
29
+ rbvmomi_datastore_opt
30
+
31
+ text <<-EOS
32
+
33
+ Other options:
34
+ EOS
35
+
36
+ opt :template_name, "Name to give to the (cached) template", :type => :string
37
+ opt :template_path, "Path where templates are stored", :default => 'templates', :type => :string
38
+ opt :computer_path, "Path to the cluster to deploy into", :type => :string
39
+ opt :network, "Name of the network to attach template to", :type => :string
40
+ opt :vm_folder_path, "Path to VM folder to deploy VM into", :type => :string
41
+ opt :lease, "Lease in days", :type => :int, :default => 3
42
+ end
43
+
44
+ Trollop.die("must specify host") unless opts[:host]
45
+ Trollop.die("no cluster path given") unless opts[:computer_path]
46
+ template_folder_path = opts[:template_path]
47
+ template_name = opts[:template_name] or Trollop.die("no template name given")
48
+ vm_name = ARGV[0] or Trollop.die("no VM name given")
49
+ ovf_url = ARGV[1] or Trollop.die("No OVF URL given")
50
+
51
+ vim = VIM.connect opts
52
+ dc = vim.serviceInstance.find_datacenter(opts[:datacenter]) or abort "datacenter not found"
53
+
54
+ root_vm_folder = dc.vmFolder
55
+ vm_folder = root_vm_folder
56
+ if opts[:vm_folder_path]
57
+ vm_folder = root_vm_folder.traverse(opts[:vm_folder_path], VIM::Folder)
58
+ end
59
+ template_folder = root_vm_folder.traverse!(template_folder_path, VIM::Folder)
60
+
61
+ scheduler = AdmissionControlledResourceScheduler.new(
62
+ vim,
63
+ :datacenter => dc,
64
+ :computer_names => [opts[:computer_path]],
65
+ :vm_folder => vm_folder,
66
+ :rp_path => '/',
67
+ :datastore_paths => [opts[:datastore]],
68
+ :max_vms_per_pod => nil, # No limits
69
+ :min_ds_free => nil, # No limits
70
+ )
71
+ scheduler.make_placement_decision
72
+
73
+ datastore = scheduler.datastore
74
+ computer = scheduler.pick_computer
75
+ # XXX: Do this properly
76
+ if opts[:network]
77
+ network = computer.network.find{|x| x.name == opts[:network]}
78
+ else
79
+ network = computer.network[0]
80
+ end
81
+
82
+ lease_tool = LeaseTool.new
83
+ lease = opts[:lease] * 24 * 60 * 60
84
+ deployer = CachedOvfDeployer.new(
85
+ vim, network, computer, template_folder, vm_folder, datastore
86
+ )
87
+ template = deployer.lookup_template template_name
88
+
89
+ if !template
90
+ puts "#{Time.now}: Uploading/Preparing OVF template ..."
91
+
92
+ template = deployer.upload_ovf_as_template(
93
+ ovf_url, template_name,
94
+ :run_without_interruptions => true,
95
+ :config => lease_tool.set_lease_in_vm_config({}, lease)
96
+ )
97
+ end
98
+
99
+ puts "#{Time.now}: Cloning template ..."
100
+ config = {
101
+ :numCPUs => opts[:cpus],
102
+ :memoryMB => opts[:memory],
103
+ }
104
+ config = lease_tool.set_lease_in_vm_config(config, lease)
105
+ vm = deployer.linked_clone template, vm_name, config
106
+
107
+ puts "#{Time.now}: Powering On VM ..."
108
+ # XXX: Add a retrying version?
109
+ vm.PowerOnVM_Task.wait_for_completion
110
+
111
+ puts "#{Time.now}: Waiting for VM to be up ..."
112
+ ip = nil
113
+ while !(ip = vm.guest_ip)
114
+ sleep 5
115
+ end
116
+
117
+ puts "#{Time.now}: VM got IP: #{ip}"
118
+
119
+ puts "#{Time.now}: Done"
120
+
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ require 'trollop'
3
+ require 'rbvmomi'
4
+ require 'rbvmomi/trollop'
5
+ require 'rbvmomi/utils/leases'
6
+ require 'yaml'
7
+
8
+ VIM = RbVmomi::VIM
9
+ CMDS = ['set_lease_on_leaseless_vms', 'show_expired_vms',
10
+ 'show_soon_expired_vms', 'kill_expired_vms']
11
+
12
+ opts = Trollop.options do
13
+ banner <<-EOS
14
+ Tool for managing leases on VMs where leases are stored in YAML on VM annotations.
15
+
16
+ Usage:
17
+ lease_tool.rb [options] <cmd>
18
+
19
+ Commands: #{CMDS * ' '}
20
+
21
+ VIM connection options:
22
+ EOS
23
+
24
+ rbvmomi_connection_opts
25
+
26
+ text <<-EOS
27
+
28
+ VM location options:
29
+ EOS
30
+
31
+ rbvmomi_datacenter_opt
32
+
33
+ text <<-EOS
34
+
35
+ Other options:
36
+ EOS
37
+
38
+ opt :vm_folder_path, "Path to VM folder to deploy VM into", :type => :string
39
+ opt :force, "Really perform VMs. Used with kill_expired_vms"
40
+
41
+ stop_on CMDS
42
+ end
43
+
44
+ Trollop.die("must specify host") unless opts[:host]
45
+ cmd = ARGV[0] or Trollop.die("no command given")
46
+ Trollop.die("no vm folder path given") unless opts[:vm_folder_path]
47
+
48
+ vim = VIM.connect opts
49
+ dc = vim.serviceInstance.find_datacenter(opts[:datacenter]) or abort "datacenter not found"
50
+
51
+ root_vm_folder = dc.vmFolder
52
+ vm_folder = root_vm_folder.traverse(opts[:vm_folder_path], VIM::Folder)
53
+
54
+ lease_tool = LeaseTool.new
55
+ vms_props_list = (['runtime.powerState'] + lease_tool.vms_props_list).uniq
56
+ inventory = vm_folder.inventory_flat('VirtualMachine' => vms_props_list)
57
+ inventory = inventory.select{|obj, props| obj.is_a?(VIM::VirtualMachine)}
58
+ case cmd
59
+ when 'set_lease_on_leaseless_vms'
60
+ lease_tool.set_lease_on_leaseless_vms(
61
+ inventory.keys, inventory,
62
+ :lease_minutes => 3 * 24 * 60 * 60 # 3 days
63
+ )
64
+ when 'show_expired_vms'
65
+ vms = lease_tool.filter_expired_vms inventory.keys, inventory
66
+ vms.each do |vm, time_to_expiration|
67
+ puts "VM '#{inventory[vm]['name']}' is expired"
68
+ end
69
+ when 'kill_expired_vms'
70
+ vms = lease_tool.filter_expired_vms inventory.keys, inventory
71
+ vms.each do |vm, time_to_expiration|
72
+ puts "VM '#{inventory[vm]['name']}' is expired"
73
+ if !opts[:force]
74
+ puts "NOT killing VM '#{inventory[vm]['name']}' because --force not set"
75
+ else
76
+ puts "Killing expired VM '#{inventory[vm]['name']}'"
77
+ # Destroying VMs is very stressful for vCenter, and we aren't in a rush
78
+ # so do one VM at a time
79
+ if inventory[vm]['runtime.powerState'] == 'poweredOn'
80
+ vm.PowerOffVM_Task.wait_for_completion
81
+ end
82
+ vm.Destroy_Task.wait_for_completion
83
+ end
84
+ end
85
+ when 'show_soon_expired_vms'
86
+ vms = lease_tool.filter_expired_vms(
87
+ inventory.keys, inventory,
88
+ :time_delta => 3.5 * 24 * 60 * 60, # 3.5 days
89
+ )
90
+ # We could send the user emails here, but for this example, just print the
91
+ # VMs that will expire within the next 3.5 days
92
+ vms.each do |vm, time_to_expiration|
93
+ if time_to_expiration > 0
94
+ hours_to_expiration = time_to_expiration / (60.0 * 60.0)
95
+ puts "VM '%s' expires in %.2fh" % [inventory[vm]['name'], hours_to_expiration]
96
+ else
97
+ puts "VM '#{inventory[vm]['name']}' is expired"
98
+ end
99
+ end
100
+ else
101
+ abort "invalid command"
102
+ end
@@ -132,10 +132,10 @@ class Connection < TrivialSoap
132
132
  end
133
133
  end
134
134
  when BasicTypes::ManagedObject
135
- fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class
135
+ fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class and not expected == BasicTypes::AnyType
136
136
  xml.tag! name, o._ref, :type => o.class.wsdl_name
137
137
  when BasicTypes::DataObject
138
- fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class
138
+ fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class and not expected == BasicTypes::AnyType
139
139
  xml.tag! name, attrs.merge("xsi:type" => o.class.wsdl_name) do
140
140
  o.class.full_props_desc.each do |desc|
141
141
  if o.props.member? desc['name'].to_sym
@@ -0,0 +1,386 @@
1
+
2
+ # An admission controlled resource scheduler for large scale vSphere deployments
3
+ #
4
+ # While DRS (Dynamic Resource Scheduler) in vSphere handles CPU and Memory
5
+ # allocations within a single vSphere cluster, larger deployments require
6
+ # another layer of scheduling to make the use of multiple clusters transparent.
7
+ # So this class doesn't replace DRS, but in fact works on top of it.
8
+ #
9
+ # The scheduler in this class performs admission control to make sure clusters
10
+ # don't get overloaded. It does so by adding additional metrics to the already
11
+ # existing CPU and Memory reservation system that DRS has. After admission
12
+ # control it also performs very basic initial placement. Note that in-cluster
13
+ # placement and load-balancing is left to DRS. Also note that no cross-cluster
14
+ # load balancing is done.
15
+ #
16
+ # This class uses the concept of a Pod: A set of clusters that share a set of
17
+ # datastores. From a datastore perspective, we are free to place a VM on any
18
+ # host or cluster. So admission control is done at the Pod level first. Pods
19
+ # are automatically dicovered based on lists of clusters and datastores.
20
+ #
21
+ # Admission control covers the following metrics:
22
+ # - Host availability: If no hosts are available within a cluster or pod,
23
+ # admission is denied.
24
+ # - Minimum free space: If a datastore falls below this free space percentage,
25
+ # admission to it will be denied. Admission to a pod is granted as long at
26
+ # least one datastore passes admission control.
27
+ # - Maximum number of VMs: If a Pod exceeds a configured number of powered on
28
+ # VMs, admission is denied. This is a crude but effective catch-all metric
29
+ # in case users didn't set proper individual CPU or Memory reservations or
30
+ # if the scalability limit doesn't originate from CPU or Memory.
31
+ #
32
+ # Placement after admission control:
33
+ # - Cluster selection: A load metric based on a combination of CPU and Memory
34
+ # load is used to always select the "least loaded" cluster. The metric is very
35
+ # crude and only meant to do very rough load balancing. If DRS clusters are
36
+ # large enough, this is good enough in most cases though.
37
+ # - Datastore selection: Right now NO intelligence is implemented here.
38
+ #
39
+ # Usage:
40
+ # Instantiate the class, call make_placement_decision and then use the exposed
41
+ # computer (cluster), resource pool, vm_folder and datastore. Currently once
42
+ # computed, a new updated placement can't be generated.
43
+ class AdmissionControlledResourceScheduler
44
+ def initialize vim, opts = {}
45
+ @vim = vim
46
+
47
+ @datacenter = opts[:datacenter]
48
+ @datacenter_path = opts[:datacenter_path]
49
+ @vm_folder = opts[:vm_folder]
50
+ @vm_folder_path = opts[:vm_folder_path]
51
+ @rp_path = opts[:rp_path]
52
+ @computers = opts[:computers]
53
+ @computer_names = opts[:computer_names]
54
+ @datastores = opts[:datastores]
55
+ @datastore_paths = opts[:datastore_paths]
56
+
57
+ @max_vms_per_pod = opts[:max_vms_per_pod]
58
+ @min_ds_free = opts[:min_ds_free]
59
+ @service_docs_url = opts[:service_docs_url]
60
+
61
+ @pc = @vim.serviceContent.propertyCollector
62
+ @root_folder = @vim.serviceContent.rootFolder
63
+ end
64
+
65
+ def log x
66
+ puts "#{Time.now}: #{x}"
67
+ end
68
+
69
+ # Returns the used VM folder. If not set yet, uses the vm_folder_path to
70
+ # lookup the folder. If it doesn't exist, it is created. Collisions between
71
+ # multiple clients concurrently creating the same folder are handled.
72
+ # @return [VIM::Folder] The VM folder
73
+ def vm_folder
74
+ retries = 1
75
+ begin
76
+ @vm_folder ||= datacenter.vmFolder.traverse!(@vm_folder_path, VIM::Folder)
77
+ if !@vm_folder
78
+ fail "VM folder #{@vm_folder_path} not found"
79
+ end
80
+ rescue RbVmomi::Fault => fault
81
+ if !fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
82
+ raise
83
+ else
84
+ retries -= 1
85
+ retry if retries >= 0
86
+ end
87
+ end
88
+ @vm_folder
89
+ end
90
+
91
+ # Returns the used Datacenter. If not set yet, uses the datacenter_path to
92
+ # lookup the datacenter.
93
+ # @return [VIM::Datacenter] The datacenter
94
+ def datacenter
95
+ if !@datacenter
96
+ @datacenter = @root_folder.traverse(@datacenter_path, VIM::Datacenter)
97
+ if !@datacenter
98
+ fail "datacenter #{@datacenter_path} not found"
99
+ end
100
+ end
101
+ @datacenter
102
+ end
103
+
104
+ # Returns the candidate datastores. If not set yet, uses the datastore_paths
105
+ # to lookup the datastores under the datacenter.
106
+ # As a side effect, also looks up properties about all the datastores
107
+ # @return [Array] List of VIM::Datastore
108
+ def datastores
109
+ if !@datastores
110
+ @datastores = @datastore_paths.map do |path|
111
+ ds = datacenter.datastoreFolder.traverse(path, VIM::Datastore)
112
+ if !ds
113
+ fail "datastore #{path} not found"
114
+ end
115
+ ds
116
+ end
117
+ end
118
+ if !@datastore_props
119
+ @datastore_props = @pc.collectMultiple(@datastores, 'summary', 'name')
120
+ end
121
+ @datastores
122
+ end
123
+
124
+ # Returns the candidate computers (aka clusters). If not set yet, uses the
125
+ # computer_names to look them up.
126
+ # @return [Array] List of [VIM::ClusterComputeResource, Hash] tuples, where
127
+ # the Hash is a list of stats about the computer
128
+ def computers
129
+ if !@computers
130
+ @computers = @computer_names.map do |name|
131
+ computer = datacenter.find_compute_resource(name)
132
+ [computer, computer.stats]
133
+ end
134
+ end
135
+ @computers
136
+ end
137
+
138
+ # Returns the candidate pods. If not set, automatically computes the pods
139
+ # based on the list of computers (aka clusters) and datastores.
140
+ # @return [Array] List of pods, where a pod is a list of VIM::ClusterComputeResource
141
+ def pods
142
+ if !@pods
143
+ # A pod is defined as a set of clusters (aka computers) that share the same
144
+ # datastore accessibility. Computing pods is done automatically using simple
145
+ # set theory math.
146
+ computersProps = @pc.collectMultiple(computers.map{|x| x[0]}, 'datastore')
147
+ @pods = computers.map do |computer, stats|
148
+ computersProps[computer]['datastore'] & self.datastores
149
+ end.uniq.map do |ds_list|
150
+ computers.map{|x| x[0]}.select do |computer|
151
+ (computer.datastore & self.datastores) == ds_list
152
+ end
153
+ end
154
+ end
155
+ @pods
156
+ end
157
+
158
+ # Returns all VMs residing with a pod. Doesn't account for templates. Does so
159
+ # very efficiently using a single API query.
160
+ # @return [Hash] Hash of VMs as keys and their properties as values.
161
+ def pod_vms pod
162
+ # This function retrieves all VMs residing inside a pod
163
+ filterSpec = VIM.PropertyFilterSpec(
164
+ objectSet: pod.map do |computer, stats|
165
+ {
166
+ obj: computer.resourcePool,
167
+ selectSet: [
168
+ VIM.TraversalSpec(
169
+ name: 'tsFolder',
170
+ type: 'ResourcePool',
171
+ path: 'resourcePool',
172
+ skip: false,
173
+ selectSet: [
174
+ VIM.SelectionSpec(name: 'tsFolder'),
175
+ VIM.SelectionSpec(name: 'tsVM'),
176
+ ]
177
+ ),
178
+ VIM.TraversalSpec(
179
+ name: 'tsVM',
180
+ type: 'ResourcePool',
181
+ path: 'vm',
182
+ skip: false,
183
+ selectSet: [],
184
+ )
185
+ ]
186
+ }
187
+ end,
188
+ propSet: [
189
+ { type: 'ResourcePool', pathSet: ['name'] },
190
+ { type: 'VirtualMachine', pathSet: %w(runtime.powerState) }
191
+ ]
192
+ )
193
+
194
+ result = @vim.propertyCollector.RetrieveProperties(specSet: [filterSpec])
195
+
196
+ out = result.map { |x| [x.obj, Hash[x.propSet.map { |y| [y.name, y.val] }]] }
197
+ out.select{|obj, props| obj.is_a?(VIM::VirtualMachine)}
198
+ end
199
+
200
+ # Returns all candidate datastores for a given pod.
201
+ # @return [Array] List of VIM::Datastore
202
+ def pod_datastores pod
203
+ pod.first.datastore & self.datastores
204
+ end
205
+
206
+ # Returns the list of pods that pass admission control. If not set yet, performs
207
+ # admission control to compute the list. If no pods passed the admission
208
+ # control, an exception is thrown.
209
+ # @return [Array] List of pods, where a pod is a list of VIM::ClusterComputeResource
210
+ def filtered_pods
211
+ # This function applies admission control and returns those pods that have
212
+ # passed admission control. An exception is thrown if access was denied to
213
+ # all pods.
214
+ if !@filtered_pods
215
+ log "Performing admission control:"
216
+ @filtered_pods = self.pods.select do |pod|
217
+ # Gather some statistics about the pod ...
218
+ on_vms = pod_vms(pod).select{|k,v| v['runtime.powerState'] == 'poweredOn'}
219
+ num_pod_vms = on_vms.length
220
+ pod_datastores = self.pod_datastores(pod)
221
+ log "Pod: #{pod.map{|x| x.name}.join(', ')}"
222
+ log " #{num_pod_vms} VMs"
223
+ pod_datastores.each do |ds|
224
+ ds_sum = @datastore_props[ds]['summary']
225
+ @datastore_props[ds]['free_percent'] = ds_sum.freeSpace.to_f * 100 / ds_sum.capacity
226
+ end
227
+ pod_datastores.each do |ds|
228
+ ds_props = @datastore_props[ds]
229
+ ds_name = ds_props['name']
230
+ free = ds_props['free_percent']
231
+ free_gb = ds_props['summary'].freeSpace.to_f / 1024**3
232
+ free_str = "%.2f GB (%.2f%%)" % [free_gb, free]
233
+ log " Datastore #{ds_name}: #{free_str} free"
234
+ end
235
+
236
+ # Admission check: VM limit
237
+ denied = false
238
+ max_vms = @max_vms_per_pod
239
+ if max_vms && max_vms > 0
240
+ if num_pod_vms > max_vms
241
+ err = "VM limit (#{max_vms}) exceeded on this Pod"
242
+ denied = true
243
+ end
244
+ end
245
+
246
+ # Admission check: Free space on datastores
247
+ min_ds_free = @min_ds_free
248
+ if min_ds_free && min_ds_free > 0
249
+ # We need at least one datastore with enough free space
250
+ low_list = pod_datastores.select do |ds|
251
+ @datastore_props[ds]['free_percent'] <= min_ds_free
252
+ end
253
+
254
+ if low_list.length == pod_datastores.length
255
+ dsNames = low_list.map{|ds| @datastore_props[ds]['name']}.join(", ")
256
+ err = "Datastores #{dsNames} below minimum free disk space (#{min_ds_free}%)"
257
+ denied = true
258
+ end
259
+ end
260
+
261
+ # Admission check: Hosts are available
262
+ if !denied
263
+ hosts_available = pod.any? do |computer|
264
+ stats = Hash[self.computers][computer]
265
+ stats[:totalCPU] > 0 && stats[:totalMem] > 0
266
+ end
267
+ if !hosts_available
268
+ err = "No hosts are current available in this pod"
269
+ denied = true
270
+ end
271
+ end
272
+
273
+ if denied
274
+ log " Admission DENIED: #{err}"
275
+ else
276
+ log " Admission granted"
277
+ end
278
+
279
+ !denied
280
+ end
281
+ end
282
+ if @filtered_pods.length == 0
283
+ log "Couldn't find any Pod with enough resources."
284
+ if @service_docs_url
285
+ log "Check #{@service_docs_url} to see which other Pods you may be able to use"
286
+ end
287
+ fail "Admission denied"
288
+ end
289
+ @filtered_pods
290
+ end
291
+
292
+ # Returns the computer (aka cluster) to be used for placement. If not set yet,
293
+ # computs the least loaded cluster (using a metric that combines CPU and Memory
294
+ # load) that passes admission control.
295
+ # @return [VIM::ClusterComputeResource] Chosen computer (aka cluster)
296
+ def pick_computer placementhint = nil
297
+ if !@computer
298
+ # Out of the pods to which we have been granted access, pick the cluster
299
+ # (aka computer) with the lowest CPU/Mem utilization for load balancing
300
+ available = self.filtered_pods.flatten
301
+ eligible = self.computers.select do |computer,stats|
302
+ available.member?(computer) && stats[:totalCPU] > 0 and stats[:totalMem] > 0
303
+ end
304
+ computer = nil
305
+ if placementhint
306
+ if eligible.length > 0
307
+ computer = eligible.map{|x| x[0]}[placementhint % eligible.length]
308
+ end
309
+ else
310
+ computer, = eligible.min_by do |computer,stats|
311
+ 2**(stats[:usedCPU].to_f/stats[:totalCPU]) + (stats[:usedMem].to_f/stats[:totalMem])
312
+ end
313
+ end
314
+
315
+ if !computer
316
+ fail "No clusters available, should have been prevented by admission control"
317
+ end
318
+ @computer = computer
319
+ end
320
+ @computer
321
+ end
322
+
323
+ # Returns the datastore to be used for placement. If not set yet, picks a
324
+ # datastore without much intelligence, as long as it passes admission control.
325
+ # @return [VIM::Datastore] Chosen datastore
326
+ def datastore placementHint = nil
327
+ pod_datastores = pick_computer.datastore & datastores
328
+
329
+ eligible = pod_datastores.select do |ds|
330
+ min_ds_free = @min_ds_free
331
+ if min_ds_free && min_ds_free > 0
332
+ ds_sum = @datastore_props[ds]['summary']
333
+ free_percent = ds_sum.freeSpace.to_f * 100 / ds_sum.capacity
334
+ free_percent > min_ds_free
335
+ else
336
+ true
337
+ end
338
+ end
339
+
340
+ if eligible.length == 0
341
+ fail "Couldn't find any eligible datastore. Admission control should have prevented this"
342
+ end
343
+
344
+ if placementHint && placementHint > 0
345
+ @datastore = eligible[placementHint % eligible.length]
346
+ else
347
+ @datastore = eligible.first
348
+ end
349
+ @datastore
350
+ end
351
+
352
+ # Runs the placement algorithm and populates all the various properties as
353
+ # a side effect. Run this first, before using the other functions of this
354
+ # class.
355
+ def make_placement_decision opts = {}
356
+ self.filtered_pods
357
+ self.pick_computer opts[:placementHint]
358
+ log "Selected compute resource: #{@computer.name}"
359
+
360
+ @rp = @computer.resourcePool.traverse(@rp_path)
361
+ if !@rp
362
+ fail "Resource pool #{@rp_path} not found"
363
+ end
364
+ log "Resource pool: #{@rp.pretty_path}"
365
+
366
+ stats = @computer.stats
367
+ if stats[:totalMem] > 0 && stats[:totalCPU] > 0
368
+ cpu_load = "#{(100*stats[:usedCPU])/stats[:totalCPU]}% cpu"
369
+ mem_load = "#{(100*stats[:usedMem])/stats[:totalMem]}% mem"
370
+ log "Cluster utilization: #{cpu_load}, #{mem_load}"
371
+ end
372
+
373
+ user_vms = vm_folder.inventory_flat('VirtualMachine' => %w(name storage)).select do |k, v|
374
+ k.is_a?(RbVmomi::VIM::VirtualMachine)
375
+ end
376
+ numVms = user_vms.length
377
+ unshared = user_vms.map do |vm, info|
378
+ info['storage'].perDatastoreUsage.map{|x| x.unshared}.inject(0, &:+)
379
+ end.inject(0, &:+)
380
+ log "User stats: #{numVms} VMs using %.2fGB of storage" % [unshared.to_f / 1024**3]
381
+
382
+ @placement_hint = opts[:placement_hint] || (rand(100) + 1)
383
+ datastore = self.datastore @placement_hint
384
+ log "Datastore: #{datastore.name}"
385
+ end
386
+ end