rbvmomi 1.0.2 → 1.1.0

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