rbvmomi 1.5.1 → 1.6.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.
@@ -0,0 +1,295 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'rbvmomi'
4
+
5
+ # The cached ovf deployer is an optimization on top of regular OVF deployment
6
+ # as it is offered by the VIM::OVFManager. Creating a VM becomes a multi-stage
7
+ # process: First the OVF is uploaded and instead of directly using it, it is
8
+ # prepared for linked cloning and marked as a template. It can then be cloned
9
+ # many times over, without the cost of repeated OVF deploys (network and storage
10
+ # IO) and the cost of storing the same base VM several times (storage space).
11
+ # Multiple concurrent users can try to follow this process and collisions are
12
+ # automatically detected and de-duplicated. One thread will win to create the
13
+ # OVF template, while the other will wait for the winning thread to finish the
14
+ # task. So even fully independent, distributed and unsynchronized clients using
15
+ # this call with be auto-synchronized just by talking to the same vCenter
16
+ # instance and using the name naming scheme for the templates.
17
+ #
18
+ # The caching concept above can be extended to multiple levels. Lets assume
19
+ # many VMs will share the same base OS, but are running different builds of the
20
+ # application running inside the VM. If it is expected that again many (but not
21
+ # all) VMs will share the same build of the application, a tree structure of
22
+ # templates becomes useful. At the root of the tree is the template with just
23
+ # the base OS. It is uploaded from an OVF if needed. Then, this base OS image
24
+ # is cloned, a particular build is installed and the resulting VM is again marked
25
+ # as a template. Users can then instantiate that particular build with very
26
+ # little extra overhead. This class supports such multi level templates via the
27
+ # :is_template parameter of linked_clone().
28
+ class CachedOvfDeployer
29
+ # Constructor. Gets the VIM connection and important VIM objects
30
+ # @param vim [VIM] VIM Connection
31
+ # @param network [VIM::Network] Network to attach templates and VMs to
32
+ # @param computer [VIM::ComputeResource] Host/Cluster to deploy templates/VMs to
33
+ # @param template_folder [VIM::Folder] Folder in which all templates are kept
34
+ # @param vm_folder [VIM::Folder] Folder into which to deploy VMs
35
+ # @param datastore [VIM::Folder] Datastore to store template/VM in
36
+ def initialize vim, network, computer, template_folder, vm_folder, datastore
37
+ @vim = vim
38
+ @network = network
39
+ @computer = computer
40
+ @rp = @computer.resourcePool
41
+ @template_folder = template_folder
42
+ @vmfolder = vm_folder
43
+ @datastore = datastore
44
+ end
45
+
46
+ def log x
47
+ # XXX: Should find a better way for users to customize how logging is done
48
+ puts "#{Time.now}: #{x}"
49
+ end
50
+
51
+ # Internal helper method that executes the passed in block while disabling
52
+ # the handling of SIGINT and SIGTERM signals. Restores their handlers after
53
+ # the block is executed.
54
+ # @param enabled [Boolean] If false, this function is a no-op
55
+ def _run_without_interruptions enabled
56
+ if enabled
57
+ int_handler = Signal.trap("SIGINT", 'IGNORE')
58
+ term_handler = Signal.trap("SIGTERM", 'IGNORE')
59
+ end
60
+
61
+ yield
62
+
63
+ if enabled
64
+ Signal.trap("SIGINT", int_handler)
65
+ Signal.trap("SIGTERM", term_handler)
66
+ end
67
+ end
68
+
69
+ # Uploads an OVF, prepares the resulting VM for linked cloning and then marks
70
+ # it as a template. If another thread happens to race to do the same task,
71
+ # the losing thread will not do the actual work, but instead wait for the
72
+ # winning thread to do the work by looking up the template VM and waiting for
73
+ # it to be marked as a template. This way, the cost of uploading and keeping
74
+ # the full size of the VM is only paid once.
75
+ # @param ovf_url [String] URL to the OVF to be deployed. Currently only http
76
+ # and https are supported
77
+ # @param template_name [String] Name of the template to be used. Should be the
78
+ # same name for the same URL. A cluster specific
79
+ # post-fix will automatically be added.
80
+ # @option opts [int] :run_without_interruptions Whether or not to disable
81
+ # SIGINT and SIGTERM during
82
+ # the OVF upload.
83
+ # @option opts [Hash] :config VM Config delta to apply after the OVF deploy is
84
+ # done. Allows the template to be customized, e.g.
85
+ # to set annotations.
86
+ # @return [VIM::VirtualMachine] The template as a VIM::VirtualMachine instance
87
+ def upload_ovf_as_template ovf_url, template_name, opts = {}
88
+ # The OVFManager expects us to know the names of the networks mentioned
89
+ # in the OVF file so we can map them to VIM::Network objects. For
90
+ # simplicity this function assumes we need to read the OVF file
91
+ # ourselves to know the names, and we map all of them to the same
92
+ # VIM::Network.
93
+ ovf = open(ovf_url, 'r'){|io| Nokogiri::XML(io.read)}
94
+ ovf.remove_namespaces!
95
+ networks = ovf.xpath('//NetworkSection/Network').map{|x| x['name']}
96
+ network_mappings = Hash[networks.map{|x| [x, @network]}]
97
+
98
+ network_mappings_str = network_mappings.map{|k, v| "#{k} = #{v.name}"}
99
+ log "networks: #{network_mappings_str.join(', ')}"
100
+
101
+ pc = @vim.serviceContent.propertyCollector
102
+
103
+ # OVFs need to be uploaded to a specific host. DRS won't just pick one
104
+ # for us, so we need to pick one wisely. The host needs to be connected,
105
+ # not be in maintenance mode and must have the destination datastore
106
+ # accessible.
107
+ hosts = @computer.host
108
+ hosts_props = pc.collectMultiple(
109
+ hosts,
110
+ 'datastore', 'runtime.connectionState',
111
+ 'runtime.inMaintenanceMode', 'name'
112
+ )
113
+ host = hosts.shuffle.find do |x|
114
+ host_props = hosts_props[x]
115
+ is_connected = host_props['runtime.connectionState'] == 'connected'
116
+ is_ds_accessible = host_props['datastore'].member?(@datastore)
117
+ is_connected && is_ds_accessible && !host_props['runtime.inMaintenanceMode']
118
+ end
119
+ if !host
120
+ fail "No host in the cluster available to upload OVF to"
121
+ end
122
+
123
+ log "Uploading OVF to #{hosts_props[host]['name']}..."
124
+ property_mappings = {}
125
+
126
+ # To work around the VMFS 8-host limit (existed until ESX 5.0), as
127
+ # well as just for organization purposes, we create one template per
128
+ # cluster. This also provides us with additional isolation.
129
+ vm_name = template_name+"-#{@computer.name}"
130
+
131
+ vm = nil
132
+ wait_for_template = false
133
+ # If the user sets opts[:run_without_interruptions], we will block
134
+ # signals from the user (SIGINT, SIGTERM) in order to not be interrupted.
135
+ # This is desirable, as other threads depend on this thread finishing
136
+ # its prepare job and thus interrupting it has impacts beyond this
137
+ # single thread or process.
138
+ _run_without_interruptions(opts[:run_without_interruptions]) do
139
+ begin
140
+ vm = @vim.serviceContent.ovfManager.deployOVF(
141
+ uri: ovf_url,
142
+ vmName: vm_name,
143
+ vmFolder: @template_folder,
144
+ host: host,
145
+ resourcePool: @rp,
146
+ datastore: @datastore,
147
+ networkMappings: network_mappings,
148
+ propertyMappings: property_mappings)
149
+ rescue RbVmomi::Fault => fault
150
+ # If two threads execute this script at the same time to upload
151
+ # the same template under the same name, one will win and the other
152
+ # with be rejected by VC. We catch those cases here, and handle
153
+ # them by waiting for the winning thread to finish preparing the
154
+ # template, see below ...
155
+ is_duplicate = fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
156
+ is_duplicate ||= (fault.fault.is_a?(RbVmomi::VIM::InvalidState) &&
157
+ !fault.fault.is_a?(RbVmomi::VIM::InvalidHostState))
158
+ if is_duplicate
159
+ wait_for_template = true
160
+ else
161
+ raise fault
162
+ end
163
+ end
164
+
165
+ # The winning thread succeeded in uploading the OVF. Now we need to
166
+ # prepare it for (linked) cloning and mark it as a template to signal
167
+ # we are done.
168
+ if !wait_for_template
169
+ vm.add_delta_disk_layer_on_all_disks
170
+ if opts[:config]
171
+ # XXX: Should we add a version that does retries?
172
+ vm.ReconfigVM_Task(:spec => opts[:config]).wait_for_completion
173
+ end
174
+ vm.MarkAsTemplate
175
+ end
176
+ end
177
+
178
+ # The losing thread now needs to wait for the winning thread to finish
179
+ # uploading and preparing the template
180
+ if wait_for_template
181
+ log "Template already exists, waiting for it to be ready"
182
+ vm = _wait_for_template_ready @template_folder, vm_name
183
+ log "Template fully prepared and ready to be cloned"
184
+ end
185
+
186
+ vm
187
+ end
188
+
189
+ # Looks up a template by name in the configured template_path. Should be used
190
+ # before uploading the VM via upload_ovf_as_template, although that is
191
+ # not strictly required, but a lot more efficient.
192
+ # @param template_name [String] Name of the template to be used. A cluster
193
+ # specific post-fix will automatically be added.
194
+ # @return [VIM::VirtualMachine] The template as a VIM::VirtualMachine instance
195
+ # or nil
196
+ def lookup_template template_name
197
+ template_path = "#{template_name}-#{@computer.name}"
198
+ template = @template_folder.traverse(template_path, VIM::VirtualMachine)
199
+ if template
200
+ is_template = template.collect 'config.template'
201
+ if !is_template
202
+ template = nil
203
+ end
204
+ end
205
+ template
206
+ end
207
+
208
+ # Creates a linked clone of a template prepared with upload_ovf_as_template.
209
+ # The function waits for completion on the clone task. Optionally, in case
210
+ # two level templates are being used, this function can wait for another
211
+ # thread to finish creating the second level template. See class comments
212
+ # for the concept of multi level templates.
213
+ # @param template_name [String] Name of the template to be used. A cluster
214
+ # specific post-fix will automatically be added.
215
+ # @param vm_name [String] Name of the new VM that is being created via cloning.
216
+ # @param config [Hash] VM Config delta to apply after the VM is cloned.
217
+ # Allows the template to be customized, e.g. to adjust
218
+ # CPU or Memory sizes or set annotations.
219
+ # @option opts [int] :is_template If true, the clone is assumed to be a template
220
+ # again and collision and de-duping logic kicks
221
+ # in.
222
+ # @return [VIM::VirtualMachine] The VIM::VirtualMachine instance of the clone
223
+ def linked_clone template_vm, vm_name, config, opts = {}
224
+ spec = {
225
+ location: {
226
+ pool: @rp,
227
+ datastore: @datastore,
228
+ diskMoveType: :moveChildMostDiskBacking,
229
+ },
230
+ powerOn: false,
231
+ template: false,
232
+ config: config,
233
+ }
234
+ if opts[:is_template]
235
+ wait_for_template = false
236
+ template_name = "#{vm_name}-#{@computer.name}"
237
+ begin
238
+ vm = template_vm.CloneVM_Task(
239
+ folder: @template_folder,
240
+ name: template_name,
241
+ spec: spec
242
+ ).wait_for_completion
243
+ rescue RbVmomi::Fault => fault
244
+ if fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
245
+ wait_for_template = true
246
+ else
247
+ raise
248
+ end
249
+ end
250
+
251
+ if wait_for_template
252
+ puts "#{Time.now}: Template already exists, waiting for it to be ready"
253
+ vm = _wait_for_template_ready @template_folder, template_name
254
+ puts "#{Time.now}: Template ready"
255
+ end
256
+ else
257
+ vm = template_vm.CloneVM_Task(
258
+ folder: @vmfolder,
259
+ name: vm_name,
260
+ spec: spec
261
+ ).wait_for_completion
262
+ end
263
+ vm
264
+ end
265
+
266
+ # Internal helper method that waits for a template to be fully created. It
267
+ # polls until it finds the VM in the inventory, and once it is there, waits
268
+ # for it to be fully created and marked as a template. This function will
269
+ # block for forever if the template never gets created or marked as a
270
+ # template.
271
+ # @param vm_folder [VIM::Folder] Folder in which we expect the template to show up
272
+ # @param vm_name [String] Name of the VM we are waiting for
273
+ # @return [VIM::VirtualMachine] The VM we were waiting for when it is ready
274
+ def _wait_for_template_ready vm_folder, vm_name
275
+ vm = nil
276
+ while !vm
277
+ sleep 3
278
+ # XXX: Optimize this
279
+ vm = vm_folder.children.find{|x| x.name == vm_name}
280
+ end
281
+ log "Template VM found"
282
+ sleep 2
283
+ while true
284
+ runtime, template = vm.collect 'runtime', 'config.template'
285
+ ready = runtime && runtime.host && runtime.powerState == "poweredOff"
286
+ ready = ready && template
287
+ if ready
288
+ break
289
+ end
290
+ sleep 5
291
+ end
292
+
293
+ vm
294
+ end
295
+ end
@@ -0,0 +1,142 @@
1
+ require 'yaml'
2
+
3
+ # A class to manage VM leases
4
+ #
5
+ # This class uses YAML encoded VM annotations (config.annotation) to manage a
6
+ # lease system. It helps add such lease info onto new and existing VMs and to
7
+ # find VMs that have expired leases or that are about to have expired leases.
8
+ # The calling code can use those to generate emails with about-to-expire
9
+ # notifications, suspend, power off or destroy VMs that have exceeded their
10
+ # lease, etc.
11
+ class LeaseTool
12
+ # Lists of VM properties the LeaseTool needs to do its job. Can be used to
13
+ # construct larger property collector calls that retrieve more info than just
14
+ # one subsystem needs.
15
+ # @return [Array] List of property names
16
+ def vms_props_list
17
+ ['name', 'config.annotation']
18
+ end
19
+
20
+ # Fetch all VM properties that the LeaseTool needs on all VMs passed in.
21
+ # @param vms [Array] List of VIM::VirtualMachine instances
22
+ # @return [Hash] Hash of VMs as keys and their properties as values
23
+ def get_vms_props vms
24
+ out = {}
25
+ if vms.length > 0
26
+ pc = vms.first._connection.serviceContent.propertyCollector
27
+ out = pc.collectMultiple(vms, 'name', 'config.annotation')
28
+ end
29
+ out
30
+ end
31
+
32
+ # Retrieve the current time as used by the lease tool.
33
+ # @return [Time] Current time as used by the lease tool
34
+ def current_time
35
+ # XXX: Should swith to time provided by VC
36
+ Time.now
37
+ end
38
+
39
+ # Helper function that sets the lease info in a passed in VM config. If there
40
+ # is no annotation, it is added. If there is an annotation, it is updated to
41
+ # include the lease info. Note that if the annotation isn't YAML, it is
42
+ # overwritten.
43
+ # @param vmconfig [Hash] Virtual Machine config spec
44
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
45
+ # @return [Hash] Updated Virtual Machine config spec
46
+ def set_lease_in_vm_config vmconfig, lease_minutes
47
+ annotation = vmconfig[:annotation]
48
+ annotation ||= ""
49
+ note = YAML.load annotation
50
+ if !note.is_a?(Hash)
51
+ note = {}
52
+ end
53
+ lease = current_time + lease_minutes * 60
54
+ note['lease'] = lease
55
+ vmconfig[:annotation] = YAML.dump(note)
56
+ vmconfig
57
+ end
58
+
59
+ # Issue ReconfigVM_Task on the VM to update the lease. User can pass in current
60
+ # annotation, but if not, it is retrieved on demand. A task is returned, i.e.
61
+ # function doesn't wait for completion.
62
+ # @param vm [VIM::VirtualMachine] Virtual Machine instance
63
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
64
+ # @param annotation [String] 'config.annotation' property of the VM. Optional.
65
+ # @return [VIM::Task] VM reconfiguration task
66
+ def set_lease_on_vm_task vm, lease_minutes, annotation = nil
67
+ if !annotation
68
+ annotation = vm.collect 'config.annotation'
69
+ end
70
+ vmconfig = {:annotation => annotation}
71
+ vmconfig = set_lease_in_vm_config vmconfig, lease_minutes
72
+ # XXX: It may be a good idea to cite the VM version here to avoid
73
+ # concurrent writes to the annotation stepping on each others toes
74
+ vm.ReconfigVM_Task(:spec => vmconfig)
75
+ end
76
+
77
+ # Issue ReconfigVM_Task to set the lease on all VMs that currently do not
78
+ # have a lease. All VM reconfigurations are done in parallel and the function
79
+ # waits for all of them to complete
80
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
81
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
82
+ # @option opts [int] :lease_minutes Time to lease expiration from now in minutes
83
+ # @return [Array] List of previously leaseless VMs that now have a lease
84
+ def set_lease_on_leaseless_vms vms, vmprops, opts = {}
85
+ lease_minutes = opts[:lease_minutes]
86
+ if !lease_minutes
87
+ raise "Expected lease_minutes to be specified"
88
+ end
89
+ vms = find_leaseless_vms vms, vmprops
90
+ if vms.length > 0
91
+ tasks = vms.map do |vm|
92
+ annotation = vmprops[vm]['config.annotation']
93
+ task = set_lease_on_vm_task(vm, lease_minutes, annotation)
94
+ task
95
+ end
96
+ si = vms.first._connection.serviceInstance
97
+ si.wait_for_multiple_tasks [], tasks
98
+ end
99
+ vms
100
+ end
101
+
102
+ # Filter the list of passed in Virtual Machines and find the ones that currently
103
+ # do not have a lease.
104
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
105
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
106
+ # @return [Array] List of leaseless VMs
107
+ def find_leaseless_vms vms, vmprops
108
+ vms.reject do |vm|
109
+ props = vmprops[vm]
110
+ annotation = props['config.annotation']
111
+ if annotation
112
+ note = YAML.load annotation
113
+ note.is_a?(Hash) && note['lease']
114
+ end
115
+ end
116
+ end
117
+
118
+ # Filter the list of passed in Virtul Machines and find the one that are
119
+ # expired. A time offset can be used to identify VMs that will expire at
120
+ # a certain point in the future.
121
+ # If a VM doesn't have a lease, it is treated as never expiring.
122
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
123
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
124
+ # @option opts [int] :time_delta Time delta (seconds) to be added to current time
125
+ # @return [Array] List of expired VMs
126
+ def filter_expired_vms vms, vmprops, opts = {}
127
+ time_delta = opts[:time_delta] || 0
128
+ time = current_time + time_delta
129
+
130
+ out = vms.map do |vm|
131
+ props = vmprops[vm]
132
+ next unless annotation = props['config.annotation']
133
+ note = YAML.load annotation
134
+ next unless note.is_a?(Hash) && lease = note['lease']
135
+ next unless time > lease
136
+ time_to_expiration = ((lease - time) + time_delta)
137
+ [vm, time_to_expiration]
138
+ end.compact
139
+ out = Hash[out]
140
+ out
141
+ end
142
+ end
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ class RbVmomi::VIM::PerfCounterInfo
22
+ def name
23
+ "#{groupInfo.key}.#{nameInfo.key}"
24
+ end
25
+ end
26
+
@@ -0,0 +1,85 @@
1
+ require 'date'
2
+
3
+ class Time
4
+ def to_datetime
5
+ # Convert seconds + microseconds into a fractional number of seconds
6
+ seconds = sec + Rational(usec, 10**6)
7
+
8
+ # Convert a UTC offset measured in minutes to one measured in a
9
+ # fraction of a day.
10
+ offset = Rational(utc_offset, 60 * 60 * 24)
11
+ DateTime.new(year, month, day, hour, min, seconds, offset)
12
+ end
13
+ end
14
+
15
+ RbVmomi::VIM::PerformanceManager
16
+ class RbVmomi::VIM::PerformanceManager
17
+ def perfcounter_cached
18
+ @perfcounter ||= perfCounter
19
+ end
20
+
21
+ def perfcounter_hash
22
+ @perfcounter_hash ||= Hash[perfcounter_cached.map{|x| [x.name, x]}]
23
+ end
24
+
25
+ def perfcounter_idhash
26
+ @perfcounter_idhash ||= Hash[perfcounter_cached.map{|x| [x.key, x]}]
27
+ end
28
+
29
+ def provider_summary obj
30
+ @provider_summary ||= {}
31
+ @provider_summary[obj.class] ||= QueryPerfProviderSummary(:entity => obj)
32
+ end
33
+
34
+ def retrieve_stats objects, metrics, opts = {}
35
+ opts = opts.dup
36
+ max_samples = opts[:max_samples] || 1
37
+ realtime = false
38
+ if not opts[:interval]
39
+ provider = provider_summary objects.first
40
+ opts[:interval] = provider.refreshRate
41
+ realtime = true
42
+ else
43
+ provider = provider_summary objects.first
44
+ if opts[:interval] == provider.refreshRate
45
+ realtime = true
46
+ end
47
+ end
48
+
49
+ metric_ids = metrics.map do |x|
50
+ counter = perfcounter_hash[x]
51
+ if !counter
52
+ pp perfcounter_hash.keys
53
+ fail "Counter for #{x} couldn't be found"
54
+ end
55
+ RbVmomi::VIM::PerfMetricId(:counterId => counter.key, :instance => '*')
56
+ end
57
+ query_specs = objects.map do |obj|
58
+ RbVmomi::VIM::PerfQuerySpec({
59
+ :maxSample => max_samples,
60
+ :entity => obj,
61
+ :metricId => metric_ids,
62
+ :intervalId => opts[:interval],
63
+ :startTime => (realtime == false ? opts[:start_time].to_datetime : nil),
64
+ })
65
+ end
66
+ stats = QueryPerf(:querySpec => query_specs)
67
+
68
+ Hash[stats.map do |res|
69
+ [
70
+ res.entity,
71
+ {
72
+ :sampleInfo => res.sampleInfo,
73
+ :metrics => Hash[res.value.map do |metric|
74
+ [perfcounter_idhash[metric.id.counterId].name, metric.value]
75
+ end]
76
+ }
77
+ ]
78
+ end]
79
+ end
80
+
81
+ def active_intervals
82
+ intervals = historicalInterval
83
+ Hash[(1..4).map { |level| [level, intervals.select { |x| x.enabled && x.level >= level }] }]
84
+ end
85
+ end
@@ -18,7 +18,11 @@ class VIM::ReflectManagedMethodExecuter
18
18
  end
19
19
  result = ExecuteSoap(:moid => moid, :version => 'urn:vim25/5.0',
20
20
  :method => method, :argument => soap_args)
21
- _connection.deserializer.deserialize Nokogiri(result.response).root, nil
21
+ if result
22
+ _connection.deserializer.deserialize Nokogiri(result.response).root, nil
23
+ else
24
+ nil
25
+ end
22
26
  end
23
27
  end
24
28
 
@@ -11,6 +11,26 @@ class RbVmomi::VIM::Task
11
11
  raise info.error
12
12
  end
13
13
  end
14
+
15
+ # Wait for all child tasks to finish. If any one child task failed,
16
+ # the exception of the first failing task is thrown.
17
+ # @return [Hash] Map of tasks to their +info.result+ on success.
18
+ # @raise +info.error+ on error.
19
+ def wait_for_childtask_completion
20
+ si = _connection.serviceInstance
21
+ tasks_props = si.wait_for_multiple_tasks(
22
+ ['info.state', 'info.result', 'info.error'],
23
+ self.child_tasks
24
+ )
25
+ Hash[tasks_props.map do |task, props|
26
+ case props['info.state']
27
+ when 'success'
28
+ [task, props['info.result']]
29
+ when 'error'
30
+ raise props['info.error']
31
+ end
32
+ end]
33
+ end
14
34
 
15
35
  # Wait for a task to finish, with progress notifications.
16
36
  # @return (see #wait_for_completion)
@@ -28,4 +48,18 @@ class RbVmomi::VIM::Task
28
48
  raise info.error
29
49
  end
30
50
  end
51
+
52
+ # Get child tasks of this task.
53
+ # @return [Array] List of VIM::Task objects
54
+ def child_tasks
55
+ tm = _connection.serviceContent.taskManager
56
+ col = tm.CreateCollectorForTasks(:filter => {
57
+ :rootTaskKey => self.info.key
58
+ })
59
+ # XXX: Likely this is not enough and we need to collect pages other
60
+ # than the latest.
61
+ tasks = col.latestPage.map{|x| x.task}
62
+ col.DestroyCollector()
63
+ tasks
64
+ end
31
65
  end
@@ -10,4 +10,50 @@ class RbVmomi::VIM::VirtualMachine
10
10
  def disks
11
11
  self.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
12
12
  end
13
+
14
+ # Get the IP of the guest, but only if it is not stale
15
+ # @return [String] Current IP reported (as per VMware Tools) or nil
16
+ def guest_ip
17
+ g = self.guest
18
+ if g.ipAddress && (g.toolsStatus == "toolsOk" || g.toolsStatus == "toolsOld")
19
+ g.ipAddress
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ # Add a layer of delta disks (redo logs) in front of every disk on the VM.
26
+ # This is similar to taking a snapshot and makes the VM a valid target for
27
+ # creating a linked clone.
28
+ #
29
+ # Background: The API for linked clones is quite strange. We can't create
30
+ # a linked straight from any VM. The disks of the VM for which we can create a
31
+ # linked clone need to be read-only and thus VC demands that the VM we
32
+ # are cloning from uses delta-disks. Only then it will allow us to
33
+ # share the base disk.
34
+ def add_delta_disk_layer_on_all_disks
35
+ devices, = self.collect 'config.hardware.device'
36
+ disks = devices.grep(RbVmomi::VIM::VirtualDisk)
37
+ # XXX: Should create a single reconfig spec instead of one per disk
38
+ disks.each do |disk|
39
+ spec = {
40
+ :deviceChange => [
41
+ {
42
+ :operation => :remove,
43
+ :device => disk
44
+ },
45
+ {
46
+ :operation => :add,
47
+ :fileOperation => :create,
48
+ :device => disk.dup.tap { |x|
49
+ x.backing = x.backing.dup
50
+ x.backing.fileName = "[#{disk.backing.datastore.name}]"
51
+ x.backing.parent = disk.backing
52
+ },
53
+ }
54
+ ]
55
+ }
56
+ self.ReconfigVM_Task(:spec => spec).wait_for_completion
57
+ end
58
+ end
13
59
  end
@@ -292,4 +292,20 @@ class SerializationTest < Test::Unit::TestCase
292
292
  <root>2011-11-16T13:36:08-08:00</root>
293
293
  EOS
294
294
  end
295
+
296
+ # TODO test all types
297
+ def test_any_type
298
+ obj = 1
299
+ check <<-EOS, obj, 'xsd:anyType', false
300
+ <root xsi:type="xsd:long">1</root>
301
+ EOS
302
+
303
+ obj = VIM::HostAccountSpec(:id => 'root', :password => 'foo')
304
+ check <<-EOS, obj, 'xsd:anyType', false
305
+ <root xsi:type="HostAccountSpec">
306
+ <id>root</id>
307
+ <password>foo</password>
308
+ </root>
309
+ EOS
310
+ end
295
311
  end
data/vmodl.db CHANGED
Binary file