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,22 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+
3
+ module Kernel
4
+ PROFILE_TIMES = Hash.new 0.0
5
+
6
+ def profile sym, &b
7
+ start_time = Time.now
8
+ ret = b.call
9
+ elapsed = Time.now - start_time
10
+ PROFILE_TIMES[sym] += elapsed
11
+ puts "#{sym} #{elapsed}s" if $profile
12
+ ret
13
+ end
14
+
15
+ def dump_profile
16
+ PROFILE_TIMES.sort_by { |k,v| -v }.each do |sym,total|
17
+ puts "#{sym} #{total}"
18
+ end
19
+ end
20
+ end
21
+
22
+ at_exit { dump_profile if $profile }
@@ -0,0 +1,29 @@
1
+ require 'trollop'
2
+
3
+ class Trollop::Parser
4
+ def rbvmomi_connection_opts
5
+ opt :host, "host", type: :string, short: 'o', default: ENV['RBVMOMI_HOST']
6
+ opt :port, "port", type: :int, short: :none, default: (ENV.member?('RBVMOMI_PORT') ? ENV['RBVMOMI_PORT'].to_i : 443)
7
+ opt :"no-ssl", "don't use ssl", short: :none, default: (ENV['RBVMOMI_SSL'] == '0')
8
+ opt :user, "username", short: 'u', default: (ENV['RBVMOMI_USER'] || 'root')
9
+ opt :password, "password", short: 'p', default: (ENV['RBVMOMI_PASSWORD'] || '')
10
+ opt :path, "SOAP endpoint path", short: :none, default: (ENV['RBVMOMI_PATH'] || '/sdk')
11
+ opt :debug, "Log SOAP messages", short: 'd'
12
+ end
13
+
14
+ def rbvmomi_datacenter_opt
15
+ opt :datacenter, "datacenter", type: :string, short: "D", default: (ENV['RBVMOMI_DATACENTER'])
16
+ end
17
+
18
+ def rbvmomi_folder_opt
19
+ opt :folder, "VM folder", type: :string, short: "F", default: (ENV['RBVMOMI_FOLDER'] || '')
20
+ end
21
+
22
+ def rbvmomi_computer_opt
23
+ opt :computer, "Compute resource", type: :string, short: "R", default: ENV['RBVMOMI_COMPUTER']
24
+ end
25
+
26
+ def rbvmomi_datastore_opt
27
+ opt :datastore, "Datastore", short: 's', default: (ENV['RBVMOMI_DATASTORE'] || 'datstore1')
28
+ end
29
+ end
@@ -0,0 +1,373 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ require 'tokyocabinet'
3
+ require 'pp'
4
+ require 'set'
5
+
6
+ include TokyoCabinet
7
+
8
+ class Class
9
+ def wsdl_name
10
+ self.class.name
11
+ end
12
+ end
13
+
14
+ module RbVmomi
15
+
16
+ module VIM
17
+
18
+ BUILTIN_TYPES = %w(ManagedObject TypeName PropertyPath ManagedObjectReference MethodName MethodFault LocalizedMethodFault)
19
+
20
+ def self.load fn
21
+ @db = HDB.new
22
+ if !@db.open(fn, HDB::OREADER | HDB::ONOLCK)
23
+ raise "VMODL db open error: #{@db.errmsg(@db.ecode)}"
24
+ end
25
+ @vmodl = Hash.new { |h,k| if e = @db[k] then h[k] = Marshal.load(e) end }
26
+ @typenames = Marshal.load(@db['_typenames']) + BUILTIN_TYPES
27
+ Object.constants.select { |x| @typenames.member? x.to_s }.each { |x| load_type x }
28
+ end
29
+
30
+ def self.has_type? name
31
+ @typenames.member? name.to_s
32
+ end
33
+
34
+ def self.type name
35
+ if has_type? name
36
+ const_get(name.to_sym)
37
+ else
38
+ fail "no such type #{name.inspect}"
39
+ end
40
+ end
41
+
42
+ def self.const_missing sym
43
+ if @typenames.member? sym.to_s
44
+ load_type sym
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def self.method_missing sym, *a
51
+ if @typenames.member? sym.to_s
52
+ const_get(sym).new *a
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def self.load_type sym
59
+ const_set sym, make_type(sym)
60
+ end
61
+
62
+ def self.make_type name
63
+ name = name.to_s
64
+ desc = @vmodl[name] or fail "unknown VIM type #{name}"
65
+ case desc['kind']
66
+ when 'data' then make_data_type name, desc
67
+ when 'managed' then make_managed_type name, desc
68
+ when 'enum' then make_enum_type name, desc
69
+ else fail desc.inspect
70
+ end
71
+ end
72
+
73
+ def self.make_data_type name, desc
74
+ superclass = const_get(desc['wsdl_base'].to_sym)
75
+ Class.new(superclass).tap do |klass|
76
+ klass.initialize name, desc['props']
77
+ end
78
+ end
79
+
80
+ def self.make_managed_type name, desc
81
+ superclass = const_get(desc['wsdl_base'].to_sym)
82
+ Class.new(superclass).tap do |klass|
83
+ klass.initialize name, desc['props'], desc['methods']
84
+ end
85
+ end
86
+
87
+ def self.make_enum_type name, desc
88
+ Class.new(Enum).tap do |klass|
89
+ klass.initialize name, desc['values']
90
+ end
91
+ end
92
+
93
+ class Base
94
+ class << self
95
+ attr_reader :wsdl_name
96
+
97
+ def initialize wsdl_name=self.name
98
+ @wsdl_name = wsdl_name
99
+ end
100
+
101
+ def to_s
102
+ @wsdl_name
103
+ end
104
+ end
105
+
106
+ initialize
107
+ end
108
+
109
+ class ObjectWithProperties < Base
110
+ class << self
111
+ attr_accessor :props_desc
112
+
113
+ def initialize name=self.name, props=[]
114
+ super name
115
+ @props_desc = props
116
+ @props_desc.each do |d|
117
+ sym = d['name'].to_sym
118
+ define_method(sym) { _get_property sym }
119
+ define_method(:"#{sym}=") { |x| _set_property sym, x }
120
+ end
121
+ end
122
+
123
+ # XXX cache
124
+ def full_props_desc
125
+ (self == ObjectWithProperties ? [] : superclass.full_props_desc) + props_desc
126
+ end
127
+
128
+ def find_prop_desc name
129
+ full_props_desc.find { |x| x['name'] == name.to_s }
130
+ end
131
+ end
132
+
133
+ def _get_property sym
134
+ fail 'unimplemented'
135
+ end
136
+
137
+ def _set_property sym, val
138
+ fail 'unimplemented'
139
+ end
140
+
141
+ initialize
142
+ end
143
+
144
+ class ObjectWithMethods < ObjectWithProperties
145
+ class << self
146
+ attr_accessor :methods_desc
147
+
148
+ def initialize name=self.name, props=[], methods={}
149
+ super name, props
150
+ @methods_desc = methods
151
+
152
+ @methods_desc.each do |k,d|
153
+ sym = k.to_sym
154
+ define_method(sym) { |*args| _call sym, *args }
155
+ define_method(:"#{sym}!") { |*args| _call sym, *args }
156
+ end
157
+ end
158
+
159
+ # XXX cache
160
+ def full_methods_desc
161
+ (self == ObjectWithMethods ? {} : superclass.full_methods_desc).merge methods_desc
162
+ end
163
+ end
164
+
165
+ initialize
166
+ end
167
+
168
+ class DataObject < ObjectWithProperties
169
+ attr_reader :props
170
+
171
+ def initialize props={}
172
+ @props = Hash[props.map { |k,v| [k.to_sym, v] }]
173
+ self.class.full_props_desc.each do |desc|
174
+ #fail "missing required property #{desc['name'].inspect} of #{self.class.wsdl_name}" if @props[desc['name'].to_sym].nil? and not desc['is-optional']
175
+ end
176
+ @props.each do |k,v|
177
+ fail "unexpected property name #{k}" unless self.class.find_prop_desc(k)
178
+ end
179
+ end
180
+
181
+ def _get_property sym
182
+ @props[sym]
183
+ end
184
+
185
+ def [] sym
186
+ _get_property sym
187
+ end
188
+
189
+ def _set_property sym, val
190
+ @props[sym] = val
191
+ end
192
+
193
+ def == o
194
+ return false unless o.class == self.class
195
+ keys = (props.keys + o.props.keys).uniq
196
+ keys.all? { |k| props[k] == o.props[k] }
197
+ end
198
+
199
+ def hash
200
+ props.hash
201
+ end
202
+
203
+ def pretty_print q
204
+ q.text self.class.name
205
+ q.group 2 do
206
+ q.text '('
207
+ q.breakable
208
+ props = @props.sort_by { |k,v| k.to_s }
209
+ q.seplist props, nil, :each do |k, v|
210
+ q.group do
211
+ q.text k.to_s
212
+ q.text ': '
213
+ q.pp v
214
+ end
215
+ end
216
+ end
217
+ q.breakable
218
+ q.text ')'
219
+ end
220
+
221
+ initialize
222
+ end
223
+
224
+ class ManagedObject < ObjectWithMethods
225
+ def initialize soap, ref
226
+ super()
227
+ @soap = soap
228
+ @ref = ref
229
+ end
230
+
231
+ def _ref
232
+ @ref
233
+ end
234
+
235
+ def _get_property sym
236
+ ret = @soap.propertyCollector.RetrieveProperties(:specSet => [{
237
+ :propSet => [{ :type => self.class.wsdl_name, :pathSet => [sym.to_s] }],
238
+ :objectSet => [{ :obj => self }],
239
+ }])[0]
240
+
241
+ if ret.propSet.empty?
242
+ fail if ret.missingSet.empty?
243
+ raise ret.missingSet[0].fault
244
+ else
245
+ ret.propSet[0].val
246
+ end
247
+ end
248
+
249
+ def _set_property sym, val
250
+ fail 'unimplemented'
251
+ end
252
+
253
+ def _call method, o={}
254
+ fail "parameters must be passed as a hash" unless o.is_a? Hash
255
+ desc = self.class.full_methods_desc[method.to_s] or fail "unknown method"
256
+ @soap.call method, desc, self, o
257
+ end
258
+
259
+ def to_s
260
+ "#{self.class.wsdl_name}(#{@ref.inspect})"
261
+ end
262
+
263
+ def pretty_print pp
264
+ pp.text to_s
265
+ end
266
+
267
+ def [] k
268
+ _get_property k
269
+ end
270
+
271
+ def == x
272
+ x.class == self.class and x._ref == @ref
273
+ end
274
+
275
+ alias eql? ==
276
+
277
+ def hash
278
+ [self.class, @ref].hash
279
+ end
280
+
281
+ initialize 'ManagedObject'
282
+ end
283
+
284
+ class Enum < Base
285
+ class << self
286
+ attr_accessor :values
287
+
288
+ def initialize name=self.name, values=[]
289
+ super name
290
+ @values = values
291
+ end
292
+ end
293
+
294
+ attr_reader :value
295
+
296
+ def initialize value
297
+ @value = value
298
+ end
299
+
300
+ initialize
301
+ end
302
+
303
+ class MethodFault < DataObject
304
+ initialize 'MethodFault', [
305
+ {
306
+ 'name' => 'faultCause',
307
+ 'wsdl_type' => 'LocalizedMethodFault',
308
+ 'is-array' => false,
309
+ 'is-optional' => true,
310
+ }, {
311
+ 'name' => 'faultMessage',
312
+ 'wsdl_type' => 'LocalizableMessage',
313
+ 'is-array' => true,
314
+ 'is-optional' => true,
315
+ },
316
+ ]
317
+ end
318
+
319
+ class LocalizedMethodFault < DataObject
320
+ initialize 'LocalizedMethodFault', [
321
+ {
322
+ 'name' => 'fault',
323
+ 'wsdl_type' => 'MethodFault',
324
+ 'is-array' => false,
325
+ 'is-optional' => false,
326
+ }, {
327
+ 'name' => 'localizedMessage',
328
+ 'wsdl_type' => 'xsd:string',
329
+ 'is-array' => false,
330
+ 'is-optional' => true,
331
+ },
332
+ ]
333
+
334
+ def exception
335
+ RbVmomi.fault self.localizedMessage, self.fault
336
+ end
337
+ end
338
+
339
+ class RuntimeFault < MethodFault
340
+ initialize
341
+ end
342
+
343
+ class MethodName < String
344
+ def self.wsdl_name; 'MethodName' end
345
+ end
346
+
347
+ class PropertyPath < String
348
+ def self.wsdl_name; 'PropertyPath' end
349
+ end
350
+
351
+ class TypeName < String
352
+ def self.wsdl_name; 'TypeName' end
353
+ end
354
+
355
+ class ManagedObjectReference
356
+ def self.wsdl_name; 'ManagedObjectReference' end
357
+ end
358
+
359
+ class ::String
360
+ def self.wsdl_name; 'xsd:string' end
361
+ end
362
+
363
+ class ::Integer
364
+ def self.wsdl_name; 'xsd:int' end
365
+ end
366
+
367
+ class ::Float
368
+ def self.wsdl_name; 'xsd:float' end
369
+ end
370
+
371
+ end
372
+
373
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ require 'rubygems'
3
+ require 'builder'
4
+ require 'nokogiri'
5
+ require 'net/http'
6
+ require 'pp'
7
+ require 'rbvmomi/profile'
8
+
9
+ class Net::HTTPGenericRequest
10
+ alias old_exec exec
11
+
12
+ def exec sock, ver, path
13
+ old_exec sock, ver, path
14
+ sock.io.flush
15
+ end
16
+ end
17
+
18
+ class TrivialSoap
19
+ attr_accessor :debug, :cookie
20
+ attr_reader :http
21
+
22
+ def initialize opts
23
+ fail unless opts.is_a? Hash
24
+ @opts = opts
25
+ return unless @opts[:host] # for testcases
26
+ @http = Net::HTTP.new(@opts[:host], @opts[:port], @opts[:proxyHost], @opts[:proxyPort])
27
+ if @opts[:ssl]
28
+ require 'net/https'
29
+ @http.use_ssl = true
30
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX
31
+ @http.cert = OpenSSL::X509::Certificate.new(@opts[:cert]) if @opts[:cert]
32
+ @http.key = OpenSSL::PKey::RSA.new(@opts[:key]) if @opts[:key]
33
+ end
34
+ @http.set_debug_output(STDERR) if $DEBUG
35
+ @http.read_timeout = 60
36
+ @http.open_timeout = 5
37
+ @http.start
38
+ @debug = @opts[:debug]
39
+ @cookie = nil
40
+ @lock = Mutex.new
41
+ end
42
+
43
+ def soap_envelope
44
+ xsd = 'http://www.w3.org/2001/XMLSchema'
45
+ env = 'http://schemas.xmlsoap.org/soap/envelope/'
46
+ xsi = 'http://www.w3.org/2001/XMLSchema-instance'
47
+ xml = Builder::XmlMarkup.new :indent => 0
48
+ xml.tag!('env:Envelope', 'xmlns:xsd' => xsd, 'xmlns:env' => env, 'xmlns:xsi' => xsi) do
49
+ xml.tag!('env:Body') do
50
+ yield xml if block_given?
51
+ end
52
+ end
53
+ xml
54
+ end
55
+
56
+ def request action, &b
57
+ headers = { 'content-type' => 'text/xml; charset=utf-8', 'SOAPAction' => action }
58
+ headers['cookie'] = @cookie if @cookie
59
+ body = soap_envelope(&b).target!
60
+
61
+ if @debug
62
+ $stderr.puts "Request:"
63
+ $stderr.puts body
64
+ $stderr.puts
65
+ end
66
+
67
+ start_time = Time.now
68
+ response = @lock.synchronize { profile(:post) { @http.request_post(@opts[:path], body, headers) } }
69
+ end_time = Time.now
70
+
71
+ @cookie = response['set-cookie'] if response.key? 'set-cookie'
72
+
73
+ nk = Nokogiri(response.body)
74
+
75
+ if @debug
76
+ $stderr.puts "Response (in #{'%.3f' % (end_time - start_time)} s)"
77
+ $stderr.puts nk
78
+ $stderr.puts
79
+ end
80
+
81
+ nk.xpath('//soapenv:Body/*').select(&:element?).first
82
+ end
83
+ end