rbvmomi 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,235 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ require 'time'
3
+
4
+ module RbVmomi
5
+
6
+ class NewDeserializer
7
+ NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
8
+
9
+ DEMANGLED_ARRAY_TYPES = {
10
+ 'AnyType' => 'xsd:anyType',
11
+ 'DateTime' => 'xsd:dateTime',
12
+ }
13
+ %w(Boolean String Byte Short Int Long Float Double).each do |x|
14
+ DEMANGLED_ARRAY_TYPES[x] = "xsd:#{x.downcase}"
15
+ end
16
+
17
+ BUILTIN_TYPE_ACTIONS = {
18
+ 'xsd:string' => :string,
19
+ 'xsd:boolean' => :boolean,
20
+ 'xsd:byte' => :int,
21
+ 'xsd:short' => :int,
22
+ 'xsd:int' => :int,
23
+ 'xsd:long' => :int,
24
+ 'xsd:float' => :float,
25
+ 'xsd:dateTime' => :date,
26
+ 'PropertyPath' => :string,
27
+ 'MethodName' => :string,
28
+ 'TypeName' => :string,
29
+ 'xsd:base64Binary' => :binary,
30
+ 'KeyValue' => :keyvalue,
31
+ }
32
+
33
+ BUILTIN_TYPE_ACTIONS.dup.each do |k,v|
34
+ if k =~ /^xsd:/
35
+ BUILTIN_TYPE_ACTIONS[$'] = v
36
+ end
37
+ end
38
+
39
+ def initialize conn
40
+ @conn = conn
41
+ @loader = conn.class.loader
42
+ end
43
+
44
+ def deserialize node, type=nil
45
+ type_attr = node['type']
46
+ type = type_attr if type_attr
47
+
48
+ if action = BUILTIN_TYPE_ACTIONS[type]
49
+ case action
50
+ when :string
51
+ node.content
52
+ when :boolean
53
+ node.content == '1' || node.content == 'true'
54
+ when :int
55
+ node.content.to_i
56
+ when :float
57
+ node.content.to_f
58
+ when :date
59
+ leaf_date node
60
+ when :binary
61
+ leaf_binary node
62
+ when :keyvalue
63
+ leaf_keyvalue node
64
+ else fail
65
+ end
66
+ else
67
+ if type =~ /^ArrayOf/
68
+ type = DEMANGLED_ARRAY_TYPES[$'] || $'
69
+ return node.children.select(&:element?).map { |c| deserialize c, type }
70
+ end
71
+
72
+ klass = @loader.get(type) or fail "no such type #{type}"
73
+ case klass.kind
74
+ when :data
75
+ traverse_data node, klass
76
+ when :enum
77
+ node.content
78
+ when :managed
79
+ leaf_managed node, klass
80
+ else fail
81
+ end
82
+ end
83
+ end
84
+
85
+ def traverse_data node, klass
86
+ obj = klass.new nil
87
+ props = obj.props
88
+ children = node.children.select(&:element?)
89
+ n = children.size
90
+ i = 0
91
+
92
+ klass.full_props_desc.each do |desc|
93
+ name = desc['name']
94
+ child_type = desc['wsdl_type']
95
+
96
+ # Ignore unknown fields
97
+ while child = children[i] and not klass.full_props_set.member? child.name
98
+ i += 1
99
+ end
100
+
101
+ if desc['is-array']
102
+ a = []
103
+ while ((child = children[i]) && (child.name == name))
104
+ child = children[i]
105
+ a << deserialize(child, child_type)
106
+ i += 1
107
+ end
108
+ props[name.to_sym] = a
109
+ elsif ((child = children[i]) && (child.name == name))
110
+ props[name.to_sym] = deserialize(child, child_type)
111
+ i += 1
112
+ end
113
+ end
114
+
115
+ obj
116
+ end
117
+
118
+ def leaf_managed node, klass
119
+ type_attr = node['type']
120
+ klass = @loader.get(type_attr) if type_attr
121
+ klass.new(@conn, node.content)
122
+ end
123
+
124
+ def leaf_date node
125
+ Time.parse node.content
126
+ end
127
+
128
+ def leaf_binary node
129
+ node.content.unpack('m')[0]
130
+ end
131
+
132
+ # XXX does the value need to be deserialized?
133
+ def leaf_keyvalue node
134
+ h = {}
135
+ node.children.each do |child|
136
+ next unless child.element?
137
+ h[child.name] = child.content
138
+ end
139
+ [h['key'], h['value']]
140
+ end
141
+ end
142
+
143
+ class OldDeserializer
144
+ NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
145
+
146
+ def initialize conn
147
+ @conn = conn
148
+ end
149
+
150
+ def deserialize xml, typename=nil
151
+ if IS_JRUBY
152
+ type_attr = xml.attribute_nodes.find { |a| a.name == 'type' &&
153
+ a.namespace &&
154
+ a.namespace.prefix == 'xsi' }
155
+ else
156
+ type_attr = xml.attribute_with_ns('type', NS_XSI)
157
+ end
158
+ typename = (type_attr || typename).to_s
159
+
160
+ if typename =~ /^ArrayOf/
161
+ typename = demangle_array_type $'
162
+ return xml.children.select(&:element?).map { |c| deserialize c, typename }
163
+ end
164
+
165
+ t = @conn.type typename
166
+ if t <= BasicTypes::DataObject
167
+ props_desc = t.full_props_desc
168
+ h = {}
169
+ props_desc.select { |d| d['is-array'] }.each { |d| h[d['name'].to_sym] = [] }
170
+ xml.children.each do |c|
171
+ next unless c.element?
172
+ field = c.name.to_sym
173
+ d = t.find_prop_desc(field.to_s) or next
174
+ o = deserialize c, d['wsdl_type']
175
+ if h[field].is_a? Array
176
+ h[field] << o
177
+ else
178
+ h[field] = o
179
+ end
180
+ end
181
+ t.new h
182
+ elsif t == BasicTypes::ManagedObjectReference
183
+ @conn.type(xml['type']).new @conn, xml.text
184
+ elsif t <= BasicTypes::ManagedObject
185
+ @conn.type(xml['type'] || t.wsdl_name).new @conn, xml.text
186
+ elsif t <= BasicTypes::Enum
187
+ xml.text
188
+ elsif t <= BasicTypes::KeyValue
189
+ h = {}
190
+ xml.children.each do |c|
191
+ next unless c.element?
192
+ h[c.name] = c.text
193
+ end
194
+ [h['key'], h['value']]
195
+ elsif t <= String
196
+ xml.text
197
+ elsif t <= Symbol
198
+ xml.text.to_sym
199
+ elsif t <= Integer
200
+ xml.text.to_i
201
+ elsif t <= Float
202
+ xml.text.to_f
203
+ elsif t <= Time
204
+ Time.parse xml.text
205
+ elsif t == BasicTypes::Boolean
206
+ xml.text == 'true' || xml.text == '1'
207
+ elsif t == BasicTypes::Binary
208
+ xml.text.unpack('m')[0]
209
+ elsif t == BasicTypes::AnyType
210
+ fail "attempted to deserialize an AnyType"
211
+ else fail "unexpected type #{t.inspect} (#{t.ancestors * '/'})"
212
+ end
213
+ rescue
214
+ $stderr.puts "#{$!.class} while deserializing #{xml.name} (#{typename}):"
215
+ $stderr.puts xml.to_s
216
+ raise
217
+ end
218
+
219
+ def demangle_array_type x
220
+ case x
221
+ when 'AnyType' then 'anyType'
222
+ when 'DateTime' then 'dateTime'
223
+ when 'Boolean', 'String', 'Byte', 'Short', 'Int', 'Long', 'Float', 'Double' then x.downcase
224
+ else x
225
+ end
226
+ end
227
+ end
228
+
229
+ if ENV['RBVMOMI_NEW_DESERIALIZER'] == '1'
230
+ Deserializer = NewDeserializer
231
+ else
232
+ Deserializer = OldDeserializer
233
+ end
234
+
235
+ end
@@ -20,6 +20,10 @@ class RbVmomi::TrivialSoap
20
20
  restart_http
21
21
  end
22
22
 
23
+ def host
24
+ @opts[:host]
25
+ end
26
+
23
27
  def close
24
28
  @http.finish rescue IOError
25
29
  end
@@ -65,10 +69,9 @@ class RbVmomi::TrivialSoap
65
69
  xml
66
70
  end
67
71
 
68
- def request action, &b
72
+ def request action, body
69
73
  headers = { 'content-type' => 'text/xml; charset=utf-8', 'SOAPAction' => action }
70
74
  headers['cookie'] = @cookie if @cookie
71
- body = soap_envelope(&b).target!
72
75
 
73
76
  if @debug
74
77
  $stderr.puts "Request:"
@@ -101,6 +104,6 @@ class RbVmomi::TrivialSoap
101
104
  $stderr.puts
102
105
  end
103
106
 
104
- nk.xpath('//soapenv:Body/*').select(&:element?).first
107
+ [nk.xpath('//soapenv:Body/*').select(&:element?).first, response.body.size]
105
108
  end
106
109
  end
@@ -5,40 +5,41 @@ require 'monitor'
5
5
  module RbVmomi
6
6
 
7
7
  class TypeLoader
8
- def initialize target, fn
9
- @target = target
8
+ def initialize fn, extension_dirs, namespace
9
+ @extension_dirs = extension_dirs
10
+ @namespace = namespace
10
11
  @lock = Monitor.new
11
12
  @db = {}
12
13
  @id2wsdl = {}
14
+ @loaded = {}
13
15
  add_types Hash[BasicTypes::BUILTIN.map { |k| [k,nil] }]
14
16
  vmodl_database = File.open(fn, 'r') { |io| Marshal.load io }
15
17
  vmodl_database.reject! { |k,v| k =~ /^_/ }
16
18
  add_types vmodl_database
19
+ preload
17
20
  end
18
21
 
19
- def init
20
- @target.constants.select { |x| has_type? x.to_s }.each { |x| load_type x.to_s }
21
- BasicTypes::BUILTIN.each do |x|
22
- @target.const_set x, BasicTypes.const_get(x)
23
- load_extension x
24
- end
25
- Object.constants.map(&:to_s).select { |x| has_type? x }.each do |x|
26
- load_type x
27
- end
22
+ def preload
23
+ names = (@namespace.constants + Object.constants).map(&:to_s).uniq.
24
+ select { |x| has? x }
25
+ names.each { |x| get(x) }
28
26
  end
29
27
 
30
- def has_type? name
28
+ def has? name
31
29
  fail unless name.is_a? String
32
- @db.member? name
30
+ @db.member?(name) or BasicTypes::BUILTIN.member?(name)
33
31
  end
34
32
 
35
- def load_type name
33
+ def get name
36
34
  fail unless name.is_a? String
35
+ return @loaded[name] if @loaded.member? name
37
36
  @lock.synchronize do
38
- @target.const_set name, make_type(name)
37
+ return @loaded[name] if @loaded.member? name
38
+ klass = make_type(name)
39
+ @namespace.const_set name, klass
39
40
  load_extension name
41
+ @loaded[name] = klass
40
42
  end
41
- nil
42
43
  end
43
44
 
44
45
  def add_types types
@@ -54,15 +55,14 @@ class TypeLoader
54
55
  private
55
56
 
56
57
  def load_extension name
57
- dirs = @target.extension_dirs
58
- dirs.map { |x| File.join(x, "#{name}.rb") }.
59
- select { |x| File.exists? x }.
60
- each { |x| load x }
58
+ @extension_dirs.map { |x| File.join(x, "#{name}.rb") }.
59
+ select { |x| File.exists? x }.
60
+ each { |x| load x }
61
61
  end
62
62
 
63
63
  def make_type name
64
64
  name = name.to_s
65
- fail if BasicTypes::BUILTIN.member? name
65
+ return BasicTypes.const_get(name) if BasicTypes::BUILTIN.member? name
66
66
  desc = @db[name] or fail "unknown VMODL type #{name}"
67
67
  case desc['kind']
68
68
  when 'data' then make_data_type name, desc
@@ -73,14 +73,14 @@ class TypeLoader
73
73
  end
74
74
 
75
75
  def make_data_type name, desc
76
- superclass = @target.const_get desc['wsdl_base']
76
+ superclass = get desc['wsdl_base']
77
77
  Class.new(superclass).tap do |klass|
78
78
  klass.init name, desc['props']
79
79
  end
80
80
  end
81
81
 
82
82
  def make_managed_type name, desc
83
- superclass = @target.const_get desc['wsdl_base']
83
+ superclass = get desc['wsdl_base']
84
84
  Class.new(superclass).tap do |klass|
85
85
  klass.init name, desc['props'], desc['methods']
86
86
  end
@@ -41,25 +41,10 @@ class VIM < Connection
41
41
  end
42
42
 
43
43
  def close
44
- VIM::SessionManager(self, 'SessionManager').Logout
44
+ VIM::SessionManager(self, 'SessionManager').Logout rescue RbVmomi::Fault
45
+ self.cookie = nil
45
46
  super
46
47
  end
47
-
48
- def cookie= cookie
49
- super
50
- ObjectSpace.undefine_finalizer self
51
- ObjectSpace.define_finalizer(self, self.class.finalizer(cookie,@opts))
52
- end
53
-
54
- def self.finalizer cookie, opts
55
- proc do |object_id|
56
- new(opts).tap do |vim|
57
- vim.instance_variable_set :@cookie, cookie
58
- vim.close
59
- end
60
- nil
61
- end
62
- end
63
48
 
64
49
  def rev= x
65
50
  super
@@ -29,7 +29,7 @@ class RbVmomi::VIM::ComputeResource
29
29
  }]
30
30
  )
31
31
 
32
- result = @soap.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
32
+ result = _connection.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
33
33
 
34
34
  stats = {
35
35
  :totalCPU => 0,
@@ -8,8 +8,8 @@ class RbVmomi::VIM::Datastore
8
8
  # @param path [String] Path on the datastore.
9
9
  def exists? path
10
10
  req = Net::HTTP::Head.new mkuripath(path)
11
- req.initialize_http_header 'cookie' => @soap.cookie
12
- resp = @soap.http.request req
11
+ req.initialize_http_header 'cookie' => _connection.cookie
12
+ resp = _connection.http.request req
13
13
  case resp
14
14
  when Net::HTTPSuccess
15
15
  true
@@ -25,10 +25,10 @@ class RbVmomi::VIM::Datastore
25
25
  # @param local_path [String] Destination path on the local machine.
26
26
  # @return [void]
27
27
  def download remote_path, local_path
28
- url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
28
+ url = "http#{_connection.http.use_ssl? ? 's' : ''}://#{_connection.http.address}:#{_connection.http.port}#{mkuripath(remote_path)}"
29
29
  pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
30
30
  "-o", local_path,
31
- "-b", @soap.cookie,
31
+ "-b", _connection.cookie,
32
32
  url,
33
33
  :out => '/dev/null'
34
34
  Process.waitpid(pid, 0)
@@ -40,10 +40,10 @@ class RbVmomi::VIM::Datastore
40
40
  # @param local_path [String] Source path on the local machine.
41
41
  # @return [void]
42
42
  def upload remote_path, local_path
43
- url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
43
+ url = "http#{_connection.http.use_ssl? ? 's' : ''}://#{_connection.http.address}:#{_connection.http.port}#{mkuripath(remote_path)}"
44
44
  pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
45
45
  "-T", local_path,
46
- "-b", @soap.cookie,
46
+ "-b", _connection.cookie,
47
47
  url,
48
48
  :out => '/dev/null'
49
49
  Process.waitpid(pid, 0)
@@ -4,7 +4,7 @@ class RbVmomi::VIM::Folder
4
4
  # @param type [Class] Return nil unless the found entity <tt>is_a? type</tt>.
5
5
  # @return [VIM::ManagedEntity]
6
6
  def find name, type=Object
7
- x = @soap.searchIndex.FindChild(:entity => self, :name => name)
7
+ x = _connection.searchIndex.FindChild(:entity => self, :name => name)
8
8
  x if x.is_a? type
9
9
  end
10
10
 
@@ -19,7 +19,7 @@ class RbVmomi::VIM::Folder
19
19
  :vmSearch => type == RbVmomi::VIM::VirtualMachine
20
20
  }
21
21
  propSpecs[:datacenter] = dc if dc
22
- x = @soap.searchIndex.FindByDnsName(propSpecs)
22
+ x = _connection.searchIndex.FindByDnsName(propSpecs)
23
23
  x if x.is_a? type
24
24
  end
25
25
 
@@ -34,7 +34,7 @@ class RbVmomi::VIM::Folder
34
34
  :vmSearch => type == RbVmomi::VIM::VirtualMachine
35
35
  }
36
36
  propSpecs[:datacenter] = dc if dc
37
- x = @soap.searchIndex.FindByIp(propSpecs)
37
+ x = _connection.searchIndex.FindByIp(propSpecs)
38
38
  x if x.is_a? type
39
39
  end
40
40
 
@@ -49,7 +49,7 @@ class RbVmomi::VIM::Folder
49
49
  :vmSearch => type == RbVmomi::VIM::VirtualMachine
50
50
  }
51
51
  propSpecs[:datacenter] = dc if dc
52
- x = @soap.searchIndex.FindByUuid(propSpecs)
52
+ x = _connection.searchIndex.FindByUuid(propSpecs)
53
53
  x if x.is_a? type
54
54
  end
55
55
 
@@ -104,21 +104,18 @@ class RbVmomi::VIM::Folder
104
104
  # included in the results. Values are an array of
105
105
  # property paths (strings) or the symbol :all.
106
106
  #
107
- # @return [Hash] Tree of inventory items. Folders are hashes from child name
108
- # to child result. Objects are hashes from property path to
109
- # value.
110
- #
111
- # @todo Return ObjectContent instead of the leaf hash.
112
- def inventory propSpecs={}
113
- propSet = [{ :type => 'Folder', :pathSet => ['name', 'parent'] }]
107
+ # @return [Hash] Hash of ManagedObjects to properties.
108
+ def inventory_flat propSpecs={}
109
+ propSet = [{ :type => 'Folder', :pathSet => ['name', 'parent', 'childEntity'] }]
114
110
  propSpecs.each do |k,v|
115
111
  case k
116
- when RbVmomi::VIM::ManagedEntity
112
+ when Class
113
+ fail "key must be a subclass of ManagedEntity" unless k < RbVmomi::VIM::ManagedEntity
117
114
  k = k.wsdl_name
118
115
  when Symbol, String
119
116
  k = k.to_s
120
117
  else
121
- fail "key must be a ManagedEntity"
118
+ fail "invalid key"
122
119
  end
123
120
 
124
121
  h = { :type => k }
@@ -150,11 +147,46 @@ class RbVmomi::VIM::Folder
150
147
  :propSet => propSet
151
148
  )
152
149
 
153
- result = @soap.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
150
+ result = _connection.propertyCollector.RetrieveProperties(:specSet => [filterSpec])
151
+ {}.tap do |h|
152
+ result.each { |r| h[r.obj] = r }
153
+ end
154
+ end
155
+
156
+ # Efficiently retrieve properties from descendants of this folder.
157
+ #
158
+ # @param propSpecs [Hash] Specification of which properties to retrieve from
159
+ # which entities. Keys may be symbols, strings, or
160
+ # classes identifying ManagedEntity subtypes to be
161
+ # included in the results. Values are an array of
162
+ # property paths (strings) or the symbol :all.
163
+ #
164
+ # @return [Hash] Tree of inventory items. Each node is a hash from
165
+ # VIM::ObjectContent to children.
166
+ def inventory_tree propSpecs={}
167
+ inv = inventory_flat propSpecs
168
+ children = inv.values.group_by { |v| v['parent'] }
169
+ rec = lambda { |parent| Hash[(children[parent]||[]).map { |x| [x, rec[x.obj]] }] }
170
+ rec[self]
171
+ end
154
172
 
173
+ # Efficiently retrieve properties from descendants of this folder.
174
+ #
175
+ # @param propSpecs [Hash] Specification of which properties to retrieve from
176
+ # which entities. Keys may be symbols, strings, or
177
+ # classes identifying ManagedEntity subtypes to be
178
+ # included in the results. Values are an array of
179
+ # property paths (strings) or the symbol :all.
180
+ #
181
+ # @return [Hash] Tree of inventory items. Folders are hashes from child name
182
+ # to child result. Objects are hashes from property path to
183
+ # value.
184
+ #
185
+ # @deprecated
186
+ def inventory propSpecs={}
187
+ inv = inventory_flat propSpecs
155
188
  tree = { self => {} }
156
- result.each do |x|
157
- obj = x.obj
189
+ inv.each do |obj,x|
158
190
  next if obj == self
159
191
  h = Hash[x.propSet.map { |y| [y.name, y.val] }]
160
192
  tree[h['parent']][h['name']] = [obj, h]