rbvmomi 1.0.1

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.
@@ -0,0 +1,491 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ CURLBIN = ENV['CURL'] || "curl"
3
+
4
+ module RbVmomi::VIM
5
+
6
+ class ManagedObject
7
+ def wait version, *pathSet
8
+ version ||= ''
9
+ all = pathSet.empty?
10
+ filter = @soap.propertyCollector.CreateFilter :spec => {
11
+ :propSet => [{ :type => self.class.wsdl_name, :all => all, :pathSet => pathSet }],
12
+ :objectSet => [{ :obj => self }],
13
+ }, :partialUpdates => false
14
+ result = @soap.propertyCollector.WaitForUpdates(version: version)
15
+ filter.DestroyPropertyFilter
16
+ changes = result.filterSet[0].objectSet[0].changeSet
17
+ changes.map { |h| [h.name.split('.').map(&:to_sym), h.val] }.each do |path,v|
18
+ k = path.pop
19
+ o = path.inject(self) { |b,k| b[k] }
20
+ o._set_property k, v unless o == self
21
+ end
22
+ result.version
23
+ end
24
+
25
+ def wait_until *pathSet, &b
26
+ ver = nil
27
+ loop do
28
+ ver = wait ver, *pathSet
29
+ if x = b.call
30
+ return x
31
+ end
32
+ end
33
+ end
34
+
35
+ def collect! *props
36
+ spec = {
37
+ objectSet: [{ obj: self }],
38
+ propSet: [{
39
+ pathSet: props,
40
+ type: self.class.wsdl_name
41
+ }]
42
+ }
43
+ @soap.propertyCollector.RetrieveProperties(specSet: [spec])[0].to_hash
44
+ end
45
+
46
+ def collect *props
47
+ h = collect! *props
48
+ a = props.map { |k| h[k.to_s] }
49
+ if block_given?
50
+ yield a
51
+ else
52
+ a
53
+ end
54
+ end
55
+ end
56
+
57
+ ManagedEntity
58
+ class ManagedEntity
59
+ def path
60
+ filterSpec = VIM.PropertyFilterSpec(
61
+ objectSet: [{
62
+ obj: self,
63
+ selectSet: [
64
+ VIM.TraversalSpec(
65
+ name: 'tsME',
66
+ type: 'ManagedEntity',
67
+ path: 'parent',
68
+ skip: false,
69
+ selectSet: [
70
+ VIM.SelectionSpec(name: 'tsME')
71
+ ]
72
+ )
73
+ ]
74
+ }],
75
+ propSet: [{
76
+ pathSet: %w(name parent),
77
+ type: 'ManagedEntity'
78
+ }]
79
+ )
80
+
81
+ result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
82
+
83
+ tree = {}
84
+ result.each { |x| tree[x.obj] = [x['parent'], x['name']] }
85
+ a = []
86
+ cur = self
87
+ while cur
88
+ parent, name = *tree[cur]
89
+ a << [cur, name]
90
+ cur = parent
91
+ end
92
+ a.reverse
93
+ end
94
+ end
95
+
96
+ Task
97
+ class Task
98
+ def wait_for_completion
99
+ wait_until('info.state') { %w(success error).member? info.state }
100
+ case info.state
101
+ when 'success'
102
+ info.result
103
+ when 'error'
104
+ raise info.error
105
+ end
106
+ end
107
+
108
+ def wait_for_progress
109
+ wait_until('info.state', 'info.progress') do
110
+ yield info.progress if block_given?
111
+ %w(success error).member? info.state
112
+ end
113
+ case info.state
114
+ when 'success'
115
+ info.result
116
+ when 'error'
117
+ raise info.error
118
+ end
119
+ end
120
+ end
121
+
122
+ Folder
123
+ class Folder
124
+ def find name, type=Object
125
+ x = @soap.searchIndex.FindChild(entity: self, name: name)
126
+ x if x.is_a? type
127
+ end
128
+
129
+ def traverse! path, type=Object
130
+ traverse path, type, true
131
+ end
132
+
133
+ def traverse path, type=Object, create=false
134
+ es = path.split('/').reject(&:empty?)
135
+ return self if es.empty?
136
+ final = es.pop
137
+
138
+ p = es.inject(self) do |f,e|
139
+ f.find(e, Folder) || (create && f.CreateFolder(name: e)) || return
140
+ end
141
+
142
+ if x = p.find(final, type)
143
+ x
144
+ elsif create and type == Folder
145
+ p.CreateFolder(name: final)
146
+ else
147
+ nil
148
+ end
149
+ end
150
+
151
+ def children
152
+ childEntity
153
+ end
154
+
155
+ def ls
156
+ Hash[children.map { |x| [x.name, x] }]
157
+ end
158
+
159
+ def inventory propSpecs={}
160
+ propSet = [{ type: 'Folder', pathSet: ['name', 'parent'] }]
161
+ propSpecs.each do |k,v|
162
+ case k
163
+ when VIM::ManagedEntity
164
+ k = k.wsdl_name
165
+ when Symbol, String
166
+ k = k.to_s
167
+ else
168
+ fail "key must be a ManagedEntity"
169
+ end
170
+
171
+ h = { type: k }
172
+ if v == :all
173
+ h[:all] = true
174
+ elsif v.is_a? Array
175
+ h[:pathSet] = v + %w(parent)
176
+ else
177
+ fail "value must be an array of property paths or :all"
178
+ end
179
+ propSet << h
180
+ end
181
+
182
+ filterSpec = VIM.PropertyFilterSpec(
183
+ objectSet: [
184
+ obj: self,
185
+ selectSet: [
186
+ VIM.TraversalSpec(
187
+ name: 'tsFolder',
188
+ type: 'Folder',
189
+ path: 'childEntity',
190
+ skip: false,
191
+ selectSet: [
192
+ VIM.SelectionSpec(name: 'tsFolder')
193
+ ]
194
+ )
195
+ ]
196
+ ],
197
+ propSet: propSet
198
+ )
199
+
200
+ result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
201
+
202
+ tree = { self => {} }
203
+ result.each do |x|
204
+ obj = x.obj
205
+ next if obj == self
206
+ h = Hash[x.propSet.map { |y| [y.name, y.val] }]
207
+ tree[h['parent']][h['name']] = [obj, h]
208
+ tree[obj] = {} if obj.is_a? VIM::Folder
209
+ end
210
+ tree
211
+ end
212
+ end
213
+
214
+ Datastore
215
+ class Datastore
216
+ def datacenter
217
+ return @datacenter if @datacenter
218
+ x = parent
219
+ while not x.is_a? Datacenter
220
+ x = x.parent
221
+ end
222
+ fail unless x.is_a? Datacenter
223
+ @datacenter = x
224
+ end
225
+
226
+ def mkuripath path
227
+ "/folder/#{URI.escape path}?dcPath=#{URI.escape datacenter.name}&dsName=#{URI.escape name}"
228
+ end
229
+
230
+ def exists? path
231
+ req = Net::HTTP::Head.new mkuripath(path)
232
+ req.initialize_http_header 'cookie' => @soap.cookie
233
+ resp = @soap.http.request req
234
+ case resp
235
+ when Net::HTTPSuccess
236
+ true
237
+ when Net::HTTPNotFound
238
+ false
239
+ else
240
+ fail resp.inspect
241
+ end
242
+ end
243
+
244
+ def download remote_path, local_path
245
+ url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
246
+ pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
247
+ "-o", local_path,
248
+ "-b", @soap.cookie,
249
+ url,
250
+ out: '/dev/null'
251
+ Process.waitpid(pid, 0)
252
+ fail "download failed" unless $?.success?
253
+ end
254
+
255
+ def upload remote_path, local_path
256
+ url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
257
+ pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
258
+ "-T", local_path,
259
+ "-b", @soap.cookie,
260
+ url,
261
+ out: '/dev/null'
262
+ Process.waitpid(pid, 0)
263
+ fail "upload failed" unless $?.success?
264
+ end
265
+ end
266
+
267
+ ServiceInstance
268
+ class ServiceInstance
269
+ def find_datacenter path=nil
270
+ if path
271
+ content.rootFolder.traverse path, VIM::Datacenter
272
+ else
273
+ content.rootFolder.childEntity.grep(VIM::Datacenter).first
274
+ end
275
+ end
276
+
277
+ def wait_for_multiple_tasks interested, tasks
278
+ version = ''
279
+ interested = (interested + ['info.state']).uniq
280
+ task_props = Hash.new { |h,k| h[k] = {} }
281
+
282
+ filter = @soap.propertyCollector.CreateFilter :spec => {
283
+ :propSet => [{ :type => 'Task', :all => false, :pathSet => interested }],
284
+ :objectSet => tasks.map { |x| { :obj => x } },
285
+ }, :partialUpdates => false
286
+
287
+ begin
288
+ until task_props.size == tasks.size and task_props.all? { |k,h| %w(success error).member? h['info.state'] }
289
+ result = @soap.propertyCollector.WaitForUpdates(version: version)
290
+ version = result.version
291
+ os = result.filterSet[0].objectSet
292
+
293
+ os.each do |o|
294
+ changes = Hash[o.changeSet.map { |x| [x.name, x.val] }]
295
+
296
+ interested.each do |k|
297
+ task = tasks.find { |x| x._ref == o.obj._ref }
298
+ task_props[task][k] = changes[k] if changes.member? k
299
+ end
300
+ end
301
+
302
+ yield task_props
303
+ end
304
+ ensure
305
+ @soap.propertyCollector.CancelWaitForUpdates
306
+ filter.DestroyPropertyFilter
307
+ end
308
+ end
309
+ end
310
+
311
+ Datacenter
312
+ class Datacenter
313
+ def find_compute_resource path=nil
314
+ if path
315
+ hostFolder.traverse path, VIM::ComputeResource
316
+ else
317
+ hostFolder.childEntity.grep(VIM::ComputeResource).first
318
+ end
319
+ end
320
+
321
+ def find_datastore name
322
+ datastore.find { |x| x.name == name }
323
+ end
324
+
325
+ def find_vm folder_path, name
326
+ vmFolder.traverse "#{folder_path}/#{name}", VIM::VirtualMachine
327
+ end
328
+ end
329
+
330
+ VirtualMachine
331
+ class VirtualMachine
332
+ def macs
333
+ Hash[self.config.hardware.device.grep(VIM::VirtualEthernetCard).map { |x| [x.deviceInfo.label, x.macAddress] }]
334
+ end
335
+ end
336
+
337
+ ObjectContent
338
+ class ObjectContent
339
+ def [](k)
340
+ to_hash[k]
341
+ end
342
+
343
+ def to_hash_uncached
344
+ h = {}
345
+ propSet.each do |x|
346
+ fail if h.member? x.name
347
+ h[x.name] = x.val
348
+ end
349
+ h
350
+ end
351
+
352
+ def to_hash
353
+ @cached_hash ||= to_hash_uncached
354
+ end
355
+ end
356
+
357
+ OvfManager
358
+ class OvfManager
359
+
360
+ # Parameters:
361
+ # uri
362
+ # vmName
363
+ # vmFolder
364
+ # host
365
+ # resourcePool
366
+ # datastore
367
+ # networkMappings = {}
368
+ # propertyMappings = {}
369
+ # diskProvisioning = :thin
370
+ def deployOVF opts={}
371
+ opts = { networkMappings: {},
372
+ propertyMappings: {},
373
+ diskProvisioning: :thin }.merge opts
374
+
375
+ %w(uri vmName vmFolder host resourcePool datastore).each do |k|
376
+ fail "parameter #{k} required" unless opts[k.to_sym]
377
+ end
378
+
379
+ ovfImportSpec = VIM::OvfCreateImportSpecParams(
380
+ hostSystem: opts[:host],
381
+ locale: "US",
382
+ entityName: opts[:vmName],
383
+ deploymentOption: "",
384
+ networkMapping: opts[:networkMappings].map{|from, to| VIM::OvfNetworkMapping(name: from, network: to)},
385
+ propertyMapping: opts[:propertyMappings].map{|key, value| VIM::KeyValue(key: key, value: value)},
386
+ diskProvisioning: opts[:diskProvisioning]
387
+ )
388
+
389
+ result = CreateImportSpec(
390
+ ovfDescriptor: open(opts[:uri]).read,
391
+ resourcePool: opts[:resourcePool],
392
+ datastore: opts[:datastore],
393
+ cisp: ovfImportSpec
394
+ )
395
+
396
+ raise result.error[0].localizedMessage if result.error && !result.error.empty?
397
+
398
+ if result.warning
399
+ result.warning.each{|x| puts "OVF Warning: #{x.localizedMessage.chomp}" }
400
+ end
401
+
402
+ nfcLease = opts[:resourcePool].ImportVApp(spec: result.importSpec,
403
+ folder: opts[:vmFolder],
404
+ host: opts[:host])
405
+
406
+ nfcLease.wait_until(:state) { nfcLease.state != "initializing" }
407
+ raise nfcLease.error if nfcLease.state == "error"
408
+
409
+ begin
410
+ nfcLease.HttpNfcLeaseProgress(percent: 5)
411
+ progress = 0.0
412
+ result.fileItem.each do |fileItem|
413
+ deviceUrl = nfcLease.info.deviceUrl.find{|x| x.importKey == fileItem.deviceId}
414
+ if !deviceUrl
415
+ raise "Couldn't find deviceURL for device '#{fileItem.deviceId}'"
416
+ end
417
+
418
+ # XXX handle file:// URIs
419
+ ovfFilename = opts[:uri].to_s
420
+ tmp = ovfFilename.split(/\//)
421
+ tmp.pop
422
+ tmp << fileItem.path
423
+ filename = tmp.join("/")
424
+
425
+ method = fileItem.create ? "PUT" : "POST"
426
+
427
+ href = deviceUrl.url.gsub("*", opts[:host].config.network.vnic[0].spec.ip.ipAddress)
428
+ downloadCmd = "#{CURLBIN} -L '#{filename}'"
429
+ uploadCmd = "#{CURLBIN} -X #{method} --insecure -T - -H 'Content-Type: application/x-vnd.vmware-streamVmdk' -H 'Content-Length: #{fileItem.size}' '#{href}'"
430
+ system("#{downloadCmd} | #{uploadCmd}")
431
+ progress += (95.0 / result.fileItem.length)
432
+ nfcLease.HttpNfcLeaseProgress(percent: progress.to_i)
433
+ end
434
+
435
+ nfcLease.HttpNfcLeaseProgress(percent: 100)
436
+ vm = nfcLease.info.entity
437
+ nfcLease.HttpNfcLeaseComplete
438
+ vm
439
+ end
440
+ rescue Exception
441
+ nfcLease.HttpNfcLeaseAbort
442
+ raise
443
+ end
444
+ end
445
+
446
+ ComputeResource
447
+ class ComputeResource
448
+ def stats
449
+ filterSpec = VIM.PropertyFilterSpec(
450
+ objectSet: [{
451
+ obj: self,
452
+ selectSet: [
453
+ VIM.TraversalSpec(
454
+ name: 'tsHosts',
455
+ type: 'ComputeResource',
456
+ path: 'host',
457
+ skip: false,
458
+ )
459
+ ]
460
+ }],
461
+ propSet: [{
462
+ pathSet: %w(name overallStatus summary.hardware.cpuMhz
463
+ summary.hardware.numCpuCores summary.hardware.memorySize
464
+ summary.quickStats.overallCpuUsage
465
+ summary.quickStats.overallMemoryUsage),
466
+ type: 'HostSystem'
467
+ }]
468
+ )
469
+
470
+ result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
471
+
472
+ stats = {
473
+ totalCPU: 0,
474
+ totalMem: 0,
475
+ usedCPU: 0,
476
+ usedMem: 0,
477
+ }
478
+
479
+ result.each do |x|
480
+ next if x['overallStatus'] == 'red'
481
+ stats[:totalCPU] += x['summary.hardware.cpuMhz'] * x['summary.hardware.numCpuCores']
482
+ stats[:totalMem] += x['summary.hardware.memorySize'] / (1024*1024)
483
+ stats[:usedCPU] += x['summary.quickStats.overallCpuUsage']
484
+ stats[:usedMem] += x['summary.quickStats.overallMemoryUsage']
485
+ end
486
+
487
+ stats
488
+ end
489
+ end
490
+
491
+ end