rbvmomi 1.0.2 → 1.1.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.
Files changed (42) hide show
  1. data/.yardopts +5 -0
  2. data/README.rdoc +71 -0
  3. data/Rakefile +11 -0
  4. data/VERSION +1 -1
  5. data/devel/analyze-xml.rb +29 -29
  6. data/{test/test.rb → examples/create_vm.rb} +40 -15
  7. data/examples/extraConfig.rb +54 -0
  8. data/examples/power.rb +57 -0
  9. data/examples/readme-1.rb +35 -0
  10. data/examples/readme-2.rb +51 -0
  11. data/lib/rbvmomi.rb +3 -282
  12. data/lib/rbvmomi/{types.rb → basic_types.rb} +37 -97
  13. data/lib/rbvmomi/connection.rb +239 -0
  14. data/lib/rbvmomi/fault.rb +17 -0
  15. data/lib/{trivial_soap.rb → rbvmomi/trivial_soap.rb} +31 -13
  16. data/lib/rbvmomi/trollop.rb +6 -2
  17. data/lib/rbvmomi/type_loader.rb +72 -0
  18. data/lib/rbvmomi/vim.rb +76 -0
  19. data/lib/rbvmomi/vim/ComputeResource.rb +51 -0
  20. data/lib/rbvmomi/vim/Datacenter.rb +17 -0
  21. data/lib/rbvmomi/vim/Datastore.rb +68 -0
  22. data/lib/rbvmomi/vim/Folder.rb +112 -0
  23. data/lib/rbvmomi/vim/ManagedEntity.rb +46 -0
  24. data/lib/rbvmomi/vim/ManagedObject.rb +55 -0
  25. data/lib/rbvmomi/vim/ObjectContent.rb +23 -0
  26. data/lib/rbvmomi/vim/ObjectUpdate.rb +23 -0
  27. data/lib/rbvmomi/vim/OvfManager.rb +93 -0
  28. data/lib/rbvmomi/vim/ResourcePool.rb +18 -0
  29. data/lib/rbvmomi/vim/ServiceInstance.rb +53 -0
  30. data/lib/rbvmomi/vim/Task.rb +31 -0
  31. data/lib/rbvmomi/vim/VirtualMachine.rb +7 -0
  32. data/test/test_deserialization.rb +11 -55
  33. data/test/test_emit_request.rb +13 -10
  34. data/test/test_exceptions.rb +16 -0
  35. data/test/test_parse_response.rb +2 -10
  36. data/test/test_serialization.rb +14 -11
  37. metadata +41 -25
  38. data/.gitignore +0 -4
  39. data/README.md +0 -12
  40. data/lib/rbvmomi/extensions.rb +0 -491
  41. data/lib/rbvmomi/profile.rb +0 -22
  42. data/test/runner.rb +0 -3
@@ -0,0 +1,239 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ require 'time'
3
+ require 'rbvmomi/trivial_soap'
4
+ require 'rbvmomi/basic_types'
5
+ require 'rbvmomi/fault'
6
+ require 'rbvmomi/type_loader'
7
+
8
+ module RbVmomi
9
+
10
+ class DeserializationFailed < Exception; end
11
+
12
+ class Connection < TrivialSoap
13
+ NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
14
+
15
+ attr_accessor :rev
16
+
17
+ def initialize opts
18
+ @ns = opts[:ns] or fail "no namespace specified"
19
+ @rev = opts[:rev] or fail "no revision specified"
20
+ super opts
21
+ end
22
+
23
+ def emit_request xml, method, descs, this, params
24
+ xml.tag! method, :xmlns => @ns do
25
+ obj2xml xml, '_this', 'ManagedObject', false, this
26
+ descs.each do |d|
27
+ k = d['name'].to_sym
28
+ if params.member? k or params.member? k.to_s
29
+ v = params.member?(k) ? params[k] : params[k.to_s]
30
+ obj2xml xml, d['name'], d['wsdl_type'], d['is-array'], v
31
+ else
32
+ fail "missing required parameter #{d['name']}" unless d['is-optional']
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def parse_response resp, desc
39
+ if resp.at('faultcode')
40
+ detail = resp.at('detail')
41
+ fault = detail && xml2obj(detail.children.first, 'MethodFault')
42
+ msg = resp.at('faultstring').text
43
+ if fault
44
+ raise RbVmomi::Fault.new(msg, fault)
45
+ else
46
+ fail "#{resp.at('faultcode').text}: #{msg}"
47
+ end
48
+ else
49
+ if desc
50
+ type = desc['is-task'] ? 'Task' : desc['wsdl_type']
51
+ returnvals = resp.children.select(&:element?).map { |c| xml2obj c, type }
52
+ desc['is-array'] ? returnvals : returnvals.first
53
+ else
54
+ nil
55
+ end
56
+ end
57
+ end
58
+
59
+ def call method, desc, this, params
60
+ fail "this is not a managed object" unless this.is_a? BasicTypes::ManagedObject
61
+ fail "parameters must be passed as a hash" unless params.is_a? Hash
62
+ fail unless desc.is_a? Hash
63
+
64
+ resp = request "#{@ns}/#{@rev}" do |xml|
65
+ emit_request xml, method, desc['params'], this, params
66
+ end
67
+
68
+ parse_response resp, desc['result']
69
+ end
70
+
71
+ def demangle_array_type x
72
+ case x
73
+ when 'AnyType' then 'anyType'
74
+ when 'DateTime' then 'dateTime'
75
+ when 'Boolean', 'String', 'Byte', 'Short', 'Int', 'Long', 'Float', 'Double' then x.downcase
76
+ else x
77
+ end
78
+ end
79
+
80
+ def xml2obj xml, typename
81
+ typename = (xml.attribute_with_ns('type', NS_XSI) || typename).to_s
82
+
83
+ if typename =~ /^ArrayOf/
84
+ typename = demangle_array_type $'
85
+ return xml.children.select(&:element?).map { |c| xml2obj c, typename }
86
+ end
87
+
88
+ t = type typename
89
+ if t <= BasicTypes::DataObject
90
+ props_desc = t.full_props_desc
91
+ h = {}
92
+ props_desc.select { |d| d['is-array'] }.each { |d| h[d['name'].to_sym] = [] }
93
+ xml.children.each do |c|
94
+ next unless c.element?
95
+ field = c.name.to_sym
96
+ d = t.find_prop_desc(field.to_s) or next
97
+ o = xml2obj c, d['wsdl_type']
98
+ if h[field].is_a? Array
99
+ h[field] << o
100
+ else
101
+ h[field] = o
102
+ end
103
+ end
104
+ t.new h
105
+ elsif t == BasicTypes::ManagedObjectReference
106
+ type(xml['type']).new self, xml.text
107
+ elsif t <= BasicTypes::ManagedObject
108
+ type(xml['type'] || t.wsdl_name).new self, xml.text
109
+ elsif t <= BasicTypes::Enum
110
+ xml.text
111
+ elsif t <= String
112
+ xml.text
113
+ elsif t <= Symbol
114
+ xml.text.to_sym
115
+ elsif t <= Integer
116
+ xml.text.to_i
117
+ elsif t <= Float
118
+ xml.text.to_f
119
+ elsif t <= Time
120
+ Time.parse xml.text
121
+ elsif t == BasicTypes::Boolean
122
+ xml.text == 'true' || xml.text == '1'
123
+ elsif t == BasicTypes::Binary
124
+ xml.text.unpack('m')[0]
125
+ elsif t == BasicTypes::AnyType
126
+ fail "attempted to deserialize an AnyType"
127
+ else fail "unexpected type #{t.inspect}"
128
+ end
129
+ end
130
+
131
+ # hic sunt dracones
132
+ def obj2xml xml, name, type, is_array, o, attrs={}
133
+ expected = type(type)
134
+ fail "expected array, got #{o.class.wsdl_name}" if is_array and not o.is_a? Array
135
+ case o
136
+ when Array
137
+ fail "expected #{expected.wsdl_name}, got array" unless is_array
138
+ o.each do |e|
139
+ obj2xml xml, name, expected.wsdl_name, false, e, attrs
140
+ end
141
+ when BasicTypes::ManagedObject
142
+ fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class
143
+ xml.tag! name, o._ref, :type => o.class.wsdl_name
144
+ when BasicTypes::DataObject
145
+ fail "expected #{expected.wsdl_name}, got #{o.class.wsdl_name} for field #{name.inspect}" if expected and not expected >= o.class
146
+ xml.tag! name, attrs.merge("xsi:type" => o.class.wsdl_name) do
147
+ o.class.full_props_desc.each do |desc|
148
+ if o.props.member? desc['name'].to_sym
149
+ v = o.props[desc['name'].to_sym]
150
+ next if v.nil?
151
+ obj2xml xml, desc['name'], desc['wsdl_type'], desc['is-array'], v
152
+ end
153
+ end
154
+ end
155
+ when BasicTypes::Enum
156
+ xml.tag! name, o.value.to_s, attrs
157
+ when Hash
158
+ fail "expected #{expected.wsdl_name}, got a hash" unless expected <= BasicTypes::DataObject
159
+ obj2xml xml, name, type, false, expected.new(o), attrs
160
+ when true, false
161
+ fail "expected #{expected.wsdl_name}, got a boolean" unless expected == BasicTypes::Boolean
162
+ attrs['xsi:type'] = 'xsd:boolean' if expected == BasicTypes::AnyType
163
+ xml.tag! name, (o ? '1' : '0'), attrs
164
+ when Symbol, String
165
+ if expected == BasicTypes::Binary
166
+ attrs['xsi:type'] = 'xsd:base64Binary' if expected == BasicTypes::AnyType
167
+ xml.tag! name, [o].pack('m').chomp, attrs
168
+ else
169
+ attrs['xsi:type'] = 'xsd:string' if expected == BasicTypes::AnyType
170
+ xml.tag! name, o.to_s, attrs
171
+ end
172
+ when Integer
173
+ attrs['xsi:type'] = 'xsd:long' if expected == BasicTypes::AnyType
174
+ xml.tag! name, o.to_s, attrs
175
+ when Float
176
+ attrs['xsi:type'] = 'xsd:double' if expected == BasicTypes::AnyType
177
+ xml.tag! name, o.to_s, attrs
178
+ when DateTime
179
+ attrs['xsi:type'] = 'xsd:dateTime' if expected == BasicTypes::AnyType
180
+ xml.tag! name, o.to_s, attrs
181
+ else fail "unexpected object class #{o.class}"
182
+ end
183
+ xml
184
+ end
185
+
186
+ def self.type name
187
+ fail unless name and (name.is_a? String or name.is_a? Symbol)
188
+ name = $' if name.to_s =~ /^xsd:/
189
+ case name.to_sym
190
+ when :anyType then BasicTypes::AnyType
191
+ when :boolean then BasicTypes::Boolean
192
+ when :string then String
193
+ when :int, :long, :short, :byte then Integer
194
+ when :float, :double then Float
195
+ when :dateTime then Time
196
+ when :base64Binary then BasicTypes::Binary
197
+ else
198
+ if @loader.has_type? name
199
+ const_get(name)
200
+ else
201
+ fail "no such type #{name.inspect}"
202
+ end
203
+ end
204
+ end
205
+
206
+ def type name
207
+ self.class.type name
208
+ end
209
+
210
+ def self.extension_path
211
+ fail "must be implemented in subclass"
212
+ end
213
+
214
+ protected
215
+
216
+ def self.const_missing sym
217
+ name = sym.to_s
218
+ if @loader and @loader.has_type? name
219
+ @loader.load_type name
220
+ const_get sym
221
+ else
222
+ super
223
+ end
224
+ end
225
+
226
+ def self.method_missing sym, *a
227
+ if @loader and @loader.has_type? sym.to_s
228
+ const_get(sym).new(*a)
229
+ else
230
+ super
231
+ end
232
+ end
233
+
234
+ def self.load_vmodl fn
235
+ @loader = RbVmomi::TypeLoader.new self, fn
236
+ end
237
+ end
238
+
239
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ module RbVmomi
3
+
4
+ class Fault < StandardError
5
+ attr_reader :fault
6
+
7
+ def initialize msg, fault
8
+ super "#{fault.class.wsdl_name}: #{msg}"
9
+ @fault = fault
10
+ end
11
+
12
+ def method_missing *a
13
+ @fault.send(*a)
14
+ end
15
+ end
16
+
17
+ end
@@ -4,18 +4,19 @@ require 'builder'
4
4
  require 'nokogiri'
5
5
  require 'net/http'
6
6
  require 'pp'
7
- require 'rbvmomi/profile'
8
7
 
9
- class Net::HTTPGenericRequest
10
- alias old_exec exec
8
+ module Net
9
+ class HTTPGenericRequest
10
+ alias old_exec exec
11
11
 
12
- def exec sock, ver, path
13
- old_exec sock, ver, path
14
- sock.io.flush
12
+ def exec sock, ver, path
13
+ old_exec sock, ver, path
14
+ sock.io.flush
15
+ end
15
16
  end
16
17
  end
17
18
 
18
- class TrivialSoap
19
+ class RbVmomi::TrivialSoap
19
20
  attr_accessor :debug, :cookie
20
21
  attr_reader :http
21
22
 
@@ -23,21 +24,31 @@ class TrivialSoap
23
24
  fail unless opts.is_a? Hash
24
25
  @opts = opts
25
26
  return unless @opts[:host] # for testcases
27
+ @debug = @opts[:debug]
28
+ @cookie = nil
29
+ @lock = Mutex.new
30
+ @http = nil
31
+ restart_http
32
+ end
33
+
34
+ def restart_http
35
+ @http.finish if @http
26
36
  @http = Net::HTTP.new(@opts[:host], @opts[:port], @opts[:proxyHost], @opts[:proxyPort])
27
37
  if @opts[:ssl]
28
38
  require 'net/https'
29
39
  @http.use_ssl = true
30
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX
40
+ if @opts[:insecure]
41
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
42
+ else
43
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
44
+ end
31
45
  @http.cert = OpenSSL::X509::Certificate.new(@opts[:cert]) if @opts[:cert]
32
46
  @http.key = OpenSSL::PKey::RSA.new(@opts[:key]) if @opts[:key]
33
47
  end
34
48
  @http.set_debug_output(STDERR) if $DEBUG
35
- @http.read_timeout = 60
49
+ @http.read_timeout = 1000000000
36
50
  @http.open_timeout = 5
37
51
  @http.start
38
- @debug = @opts[:debug]
39
- @cookie = nil
40
- @lock = Mutex.new
41
52
  end
42
53
 
43
54
  def soap_envelope
@@ -65,7 +76,14 @@ class TrivialSoap
65
76
  end
66
77
 
67
78
  start_time = Time.now
68
- response = @lock.synchronize { profile(:post) { @http.request_post(@opts[:path], body, headers) } }
79
+ response = @lock.synchronize do
80
+ begin
81
+ @http.request_post(@opts[:path], body, headers)
82
+ rescue Exception
83
+ restart_http
84
+ raise
85
+ end
86
+ end
69
87
  end_time = Time.now
70
88
 
71
89
  @cookie = response['set-cookie'] if response.key? 'set-cookie'
@@ -1,10 +1,13 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
1
2
  require 'trollop'
2
3
 
3
- class Trollop::Parser
4
+ module Trollop
5
+ class Parser
4
6
  def rbvmomi_connection_opts
5
7
  opt :host, "host", type: :string, short: 'o', default: ENV['RBVMOMI_HOST']
6
8
  opt :port, "port", type: :int, short: :none, default: (ENV.member?('RBVMOMI_PORT') ? ENV['RBVMOMI_PORT'].to_i : 443)
7
9
  opt :"no-ssl", "don't use ssl", short: :none, default: (ENV['RBVMOMI_SSL'] == '0')
10
+ opt :insecure, "don't verify ssl certificate", short: 'k', default: (ENV['RBVMOMI_INSECURE'] == '1')
8
11
  opt :user, "username", short: 'u', default: (ENV['RBVMOMI_USER'] || 'root')
9
12
  opt :password, "password", short: 'p', default: (ENV['RBVMOMI_PASSWORD'] || '')
10
13
  opt :path, "SOAP endpoint path", short: :none, default: (ENV['RBVMOMI_PATH'] || '/sdk')
@@ -12,7 +15,7 @@ class Trollop::Parser
12
15
  end
13
16
 
14
17
  def rbvmomi_datacenter_opt
15
- opt :datacenter, "datacenter", type: :string, short: "D", default: (ENV['RBVMOMI_DATACENTER'])
18
+ opt :datacenter, "datacenter", type: :string, short: "D", default: (ENV['RBVMOMI_DATACENTER'] || 'ha-datacenter')
16
19
  end
17
20
 
18
21
  def rbvmomi_folder_opt
@@ -27,3 +30,4 @@ class Trollop::Parser
27
30
  opt :datastore, "Datastore", short: 's', default: (ENV['RBVMOMI_DATASTORE'] || 'datastore1')
28
31
  end
29
32
  end
33
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
+ require 'cdb'
3
+ require 'set'
4
+
5
+ module RbVmomi
6
+
7
+ class TypeLoader
8
+ def initialize target, fn
9
+ @target = target
10
+ @db = CDB.new fn
11
+ @vmodl = Hash.new { |h,k| if e = @db[k] then h[k] = Marshal.load(e) end }
12
+ @typenames = Set.new(Marshal.load(@db['_typenames']) + BasicTypes::BUILTIN)
13
+ @target.constants.select { |x| has_type? x.to_s }.each { |x| load_type x.to_s }
14
+ BasicTypes::BUILTIN.each do |x|
15
+ target.const_set x, BasicTypes.const_get(x)
16
+ load_extension x
17
+ end
18
+ end
19
+
20
+ def has_type? name
21
+ fail unless name.is_a? String
22
+ @typenames.member? name
23
+ end
24
+
25
+ def load_type name
26
+ fail unless name.is_a? String
27
+ @target.const_set name, make_type(name)
28
+ load_extension name
29
+ nil
30
+ end
31
+
32
+ private
33
+
34
+ def load_extension name
35
+ path = @target.extension_path name
36
+ load path if File.exists? path
37
+ end
38
+
39
+ def make_type name
40
+ name = name.to_s
41
+ fail if BasicTypes::BUILTIN.member? name
42
+ desc = @vmodl[name] or fail "unknown VMODL type #{name}"
43
+ case desc['kind']
44
+ when 'data' then make_data_type name, desc
45
+ when 'managed' then make_managed_type name, desc
46
+ when 'enum' then make_enum_type name, desc
47
+ else fail desc.inspect
48
+ end
49
+ end
50
+
51
+ def make_data_type name, desc
52
+ superclass = @target.const_get desc['wsdl_base']
53
+ Class.new(superclass).tap do |klass|
54
+ klass.init name, desc['props']
55
+ end
56
+ end
57
+
58
+ def make_managed_type name, desc
59
+ superclass = @target.const_get desc['wsdl_base']
60
+ Class.new(superclass).tap do |klass|
61
+ klass.init name, desc['props'], desc['methods']
62
+ end
63
+ end
64
+
65
+ def make_enum_type name, desc
66
+ Class.new(BasicTypes::Enum).tap do |klass|
67
+ klass.init name, desc['values']
68
+ end
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,76 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ require 'rbvmomi'
3
+
4
+ module RbVmomi
5
+
6
+ # A connection to one vSphere SDK endpoint.
7
+ # @see #serviceInstance
8
+ class VIM < Connection
9
+ # Connect to a vSphere SDK endpoint
10
+ #
11
+ # @param [Hash] opts The options hash.
12
+ # @option opts [String] :host Host to connect to.
13
+ # @option opts [Numeric] :port (443) Port to connect to.
14
+ # @option opts [Boolean] :ssl (true) Whether to use SSL.
15
+ # @option opts [Boolean] :insecure (false) If true, ignore SSL certificate errors.
16
+ # @option opts [String] :user (root) Username.
17
+ # @option opts [String] :password Password.
18
+ # @option opts [String] :path (/sdk) SDK endpoint path.
19
+ # @option opts [Boolean] :debug (false) If true, print SOAP traffic to stderr.
20
+ def self.connect opts
21
+ fail unless opts.is_a? Hash
22
+ fail "host option required" unless opts[:host]
23
+ opts[:user] ||= 'root'
24
+ opts[:password] ||= ''
25
+ opts[:ssl] = true unless opts.member? :ssl
26
+ opts[:insecure] ||= false
27
+ opts[:port] ||= (opts[:ssl] ? 443 : 80)
28
+ opts[:path] ||= '/sdk'
29
+ opts[:ns] ||= 'urn:vim25'
30
+ opts[:rev] = '4.1'
31
+ opts[:debug] = (!ENV['RBVMOMI_DEBUG'].empty? rescue false) unless opts.member? :debug
32
+
33
+ new(opts).tap do |vim|
34
+ vim.serviceContent.sessionManager.Login :userName => opts[:user], :password => opts[:password]
35
+ end
36
+ end
37
+
38
+ # Return the ServiceInstance
39
+ #
40
+ # The ServiceInstance is the root of the vSphere inventory.
41
+ # @see http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.ServiceInstance.html
42
+ def serviceInstance
43
+ VIM::ServiceInstance self, 'ServiceInstance'
44
+ end
45
+
46
+ # Alias to serviceInstance.RetrieveServiceContent
47
+ def serviceContent
48
+ @serviceContent ||= serviceInstance.RetrieveServiceContent
49
+ end
50
+
51
+ # Alias to serviceContent.rootFolder
52
+ def rootFolder
53
+ serviceContent.rootFolder
54
+ end
55
+
56
+ alias root rootFolder
57
+
58
+ # Alias to serviceContent.propertyCollector
59
+ def propertyCollector
60
+ serviceContent.propertyCollector
61
+ end
62
+
63
+ # Alias to serviceContent.searchIndex
64
+ def searchIndex
65
+ serviceContent.searchIndex
66
+ end
67
+
68
+ # @private
69
+ def self.extension_path name
70
+ File.join(File.dirname(__FILE__), "vim", "#{name}.rb")
71
+ end
72
+
73
+ load_vmodl(ENV['VMODL'] || File.join(File.dirname(__FILE__), "../../vmodl.cdb"))
74
+ end
75
+
76
+ end