rbvmomi 1.0.1

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