rbvmomi2 3.0.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +114 -0
  4. data/exe/rbvmomish +138 -0
  5. data/lib/rbvmomi/basic_types.rb +383 -0
  6. data/lib/rbvmomi/connection.rb +272 -0
  7. data/lib/rbvmomi/deserialization.rb +249 -0
  8. data/lib/rbvmomi/fault.rb +19 -0
  9. data/lib/rbvmomi/optimist.rb +72 -0
  10. data/lib/rbvmomi/pbm.rb +68 -0
  11. data/lib/rbvmomi/sms/SmsStorageManager.rb +10 -0
  12. data/lib/rbvmomi/sms.rb +63 -0
  13. data/lib/rbvmomi/sso.rb +313 -0
  14. data/lib/rbvmomi/trivial_soap.rb +122 -0
  15. data/lib/rbvmomi/type_loader.rb +138 -0
  16. data/lib/rbvmomi/utils/admission_control.rb +401 -0
  17. data/lib/rbvmomi/utils/deploy.rb +318 -0
  18. data/lib/rbvmomi/utils/leases.rb +145 -0
  19. data/lib/rbvmomi/utils/perfdump.rb +631 -0
  20. data/lib/rbvmomi/version.rb +6 -0
  21. data/lib/rbvmomi/vim/ComputeResource.rb +54 -0
  22. data/lib/rbvmomi/vim/Datacenter.rb +25 -0
  23. data/lib/rbvmomi/vim/Datastore.rb +72 -0
  24. data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +78 -0
  25. data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +23 -0
  26. data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +54 -0
  27. data/lib/rbvmomi/vim/Folder.rb +214 -0
  28. data/lib/rbvmomi/vim/HostSystem.rb +177 -0
  29. data/lib/rbvmomi/vim/ManagedEntity.rb +60 -0
  30. data/lib/rbvmomi/vim/ManagedObject.rb +63 -0
  31. data/lib/rbvmomi/vim/ObjectContent.rb +26 -0
  32. data/lib/rbvmomi/vim/ObjectUpdate.rb +26 -0
  33. data/lib/rbvmomi/vim/OvfManager.rb +204 -0
  34. data/lib/rbvmomi/vim/PerfCounterInfo.rb +28 -0
  35. data/lib/rbvmomi/vim/PerformanceManager.rb +113 -0
  36. data/lib/rbvmomi/vim/PropertyCollector.rb +28 -0
  37. data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +33 -0
  38. data/lib/rbvmomi/vim/ResourcePool.rb +58 -0
  39. data/lib/rbvmomi/vim/ServiceInstance.rb +58 -0
  40. data/lib/rbvmomi/vim/Task.rb +68 -0
  41. data/lib/rbvmomi/vim/VirtualMachine.rb +75 -0
  42. data/lib/rbvmomi/vim.rb +157 -0
  43. data/lib/rbvmomi.rb +16 -0
  44. data/lib/rbvmomi2.rb +3 -0
  45. data/vmodl.db +0 -0
  46. metadata +214 -0
@@ -0,0 +1,318 @@
1
+ # Copyright (c) 2012-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ require 'open-uri'
5
+ require 'nokogiri'
6
+ require_relative '../../rbvmomi'
7
+
8
+ # The cached ovf deployer is an optimization on top of regular OVF deployment
9
+ # as it is offered by the VIM::OVFManager. Creating a VM becomes a multi-stage
10
+ # process: First the OVF is uploaded and instead of directly using it, it is
11
+ # prepared for linked cloning and marked as a template. It can then be cloned
12
+ # many times over, without the cost of repeated OVF deploys (network and storage
13
+ # IO) and the cost of storing the same base VM several times (storage space).
14
+ # Multiple concurrent users can try to follow this process and collisions are
15
+ # automatically detected and de-duplicated. One thread will win to create the
16
+ # OVF template, while the other will wait for the winning thread to finish the
17
+ # task. So even fully independent, distributed and unsynchronized clients using
18
+ # this call with be auto-synchronized just by talking to the same vCenter
19
+ # instance and using the name naming scheme for the templates.
20
+ #
21
+ # The caching concept above can be extended to multiple levels. Lets assume
22
+ # many VMs will share the same base OS, but are running different builds of the
23
+ # application running inside the VM. If it is expected that again many (but not
24
+ # all) VMs will share the same build of the application, a tree structure of
25
+ # templates becomes useful. At the root of the tree is the template with just
26
+ # the base OS. It is uploaded from an OVF if needed. Then, this base OS image
27
+ # is cloned, a particular build is installed and the resulting VM is again marked
28
+ # as a template. Users can then instantiate that particular build with very
29
+ # little extra overhead. This class supports such multi level templates via the
30
+ # :is_template parameter of linked_clone().
31
+ class CachedOvfDeployer
32
+ # Constructor. Gets the VIM connection and important VIM objects
33
+ # @param vim [VIM] VIM Connection
34
+ # @param network [VIM::Network] Network to attach templates and VMs to
35
+ # @param computer [VIM::ComputeResource] Host/Cluster to deploy templates/VMs to
36
+ # @param template_folder [VIM::Folder] Folder in which all templates are kept
37
+ # @param vm_folder [VIM::Folder] Folder into which to deploy VMs
38
+ # @param datastore [VIM::Folder] Datastore to store template/VM in
39
+ # @param opts [Hash] Additional parameters
40
+ def initialize vim, network, computer, template_folder, vm_folder, datastore, opts = {}
41
+ @vim = vim
42
+ @network = network
43
+ @computer = computer
44
+ @rp = @computer.resourcePool
45
+ @template_folder = template_folder
46
+ @vmfolder = vm_folder
47
+ @datastore = datastore
48
+ @logger = opts[:logger]
49
+ end
50
+
51
+ def log x
52
+ if @logger
53
+ @logger.info x
54
+ else
55
+ puts "#{Time.now}: #{x}"
56
+ end
57
+ end
58
+
59
+ # Internal helper method that executes the passed in block while disabling
60
+ # the handling of SIGINT and SIGTERM signals. Restores their handlers after
61
+ # the block is executed.
62
+ # @param enabled [Boolean] If false, this function is a no-op
63
+ def _run_without_interruptions enabled
64
+ if enabled
65
+ int_handler = Signal.trap("SIGINT", 'IGNORE')
66
+ term_handler = Signal.trap("SIGTERM", 'IGNORE')
67
+ end
68
+
69
+ yield
70
+
71
+ if enabled
72
+ Signal.trap("SIGINT", int_handler)
73
+ Signal.trap("SIGTERM", term_handler)
74
+ end
75
+ end
76
+
77
+ # Uploads an OVF, prepares the resulting VM for linked cloning and then marks
78
+ # it as a template. If another thread happens to race to do the same task,
79
+ # the losing thread will not do the actual work, but instead wait for the
80
+ # winning thread to do the work by looking up the template VM and waiting for
81
+ # it to be marked as a template. This way, the cost of uploading and keeping
82
+ # the full size of the VM is only paid once.
83
+ # @param ovf_url [String] URL to the OVF to be deployed. Currently only http
84
+ # and https are supported.
85
+ # @param template_name [String] Name of the template to be used. Should be the
86
+ # same name for the same URL. A cluster specific post-fix will automatically
87
+ # be added.
88
+ # @option opts [int] :run_without_interruptions Whether or not to disable
89
+ # SIGINT and SIGTERM during the OVF upload.
90
+ # @option opts [Hash] :config VM Config delta to apply after the OVF deploy is
91
+ # done. Allows the template to be customized, e.g. to set annotations.
92
+ # @option opts [Boolean] :no_delta Whether or not to add a delta disk layer.
93
+ # @return [VIM::VirtualMachine] The template as a VIM::VirtualMachine instance
94
+ def upload_ovf_as_template(ovf_url, template_name, opts = {})
95
+ # Optimization: If there happens to be a fully prepared template, then
96
+ # there is no need to do the complicated OVF upload dance
97
+ template = lookup_template template_name
98
+ if template
99
+ return template
100
+ end
101
+
102
+ # The OVFManager expects us to know the names of the networks mentioned
103
+ # in the OVF file so we can map them to VIM::Network objects. For
104
+ # simplicity this function assumes we need to read the OVF file
105
+ # ourselves to know the names, and we map all of them to the same
106
+ # VIM::Network.
107
+
108
+ # If we're handling a file:// URI we need to strip the scheme as open-uri
109
+ # can't handle them.
110
+ if URI(ovf_url).scheme == "file" && URI(ovf_url).host.nil?
111
+ ovf_url = URI(ovf_url).path
112
+ end
113
+
114
+ ovf = open(ovf_url, 'r'){|io| Nokogiri::XML(io.read)}
115
+ ovf.remove_namespaces!
116
+ networks = ovf.xpath('//NetworkSection/Network').map{|x| x['name']}
117
+ network_mappings = Hash[networks.map{|x| [x, @network]}]
118
+
119
+ network_mappings_str = network_mappings.map{|k, v| "#{k} = #{v.name}"}
120
+ log "networks: #{network_mappings_str.join(', ')}"
121
+
122
+ pc = @vim.serviceContent.propertyCollector
123
+
124
+ # OVFs need to be uploaded to a specific host. DRS won't just pick one
125
+ # for us, so we need to pick one wisely. The host needs to be connected,
126
+ # not be in maintenance mode and must have the destination datastore
127
+ # accessible.
128
+ hosts = @computer.host
129
+ hosts_props = pc.collectMultiple(
130
+ hosts,
131
+ 'datastore', 'runtime.connectionState',
132
+ 'runtime.inMaintenanceMode', 'name'
133
+ )
134
+ host = hosts.shuffle.find do |x|
135
+ host_props = hosts_props[x]
136
+ is_connected = host_props['runtime.connectionState'] == 'connected'
137
+ is_ds_accessible = host_props['datastore'].member?(@datastore)
138
+ is_connected && is_ds_accessible && !host_props['runtime.inMaintenanceMode']
139
+ end
140
+ if !host
141
+ fail "No host in the cluster available to upload OVF to"
142
+ end
143
+
144
+ log "Uploading OVF to #{hosts_props[host]['name']}..."
145
+ property_mappings = {}
146
+
147
+ # To work around the VMFS 8-host limit (existed until ESX 5.0), as
148
+ # well as just for organization purposes, we create one template per
149
+ # cluster. This also provides us with additional isolation.
150
+ vm_name = template_name+"-#{@computer.name}"
151
+
152
+ vm = nil
153
+ wait_for_template = false
154
+ # If the user sets opts[:run_without_interruptions], we will block
155
+ # signals from the user (SIGINT, SIGTERM) in order to not be interrupted.
156
+ # This is desirable, as other threads depend on this thread finishing
157
+ # its prepare job and thus interrupting it has impacts beyond this
158
+ # single thread or process.
159
+ _run_without_interruptions(opts[:run_without_interruptions]) do
160
+ begin
161
+ vm = @vim.serviceContent.ovfManager.deployOVF(
162
+ uri: ovf_url,
163
+ vmName: vm_name,
164
+ vmFolder: @template_folder,
165
+ host: host,
166
+ resourcePool: @rp,
167
+ datastore: @datastore,
168
+ networkMappings: network_mappings,
169
+ propertyMappings: property_mappings)
170
+ rescue RbVmomi::Fault => fault
171
+ # If two threads execute this script at the same time to upload
172
+ # the same template under the same name, one will win and the other
173
+ # with be rejected by VC. We catch those cases here, and handle
174
+ # them by waiting for the winning thread to finish preparing the
175
+ # template, see below ...
176
+ is_duplicate = fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
177
+ is_duplicate ||= (fault.fault.is_a?(RbVmomi::VIM::InvalidState) &&
178
+ !fault.fault.is_a?(RbVmomi::VIM::InvalidHostState))
179
+ if is_duplicate
180
+ wait_for_template = true
181
+ else
182
+ raise fault
183
+ end
184
+ end
185
+
186
+ # The winning thread succeeded in uploading the OVF. Now we need to
187
+ # prepare it for (linked) cloning and mark it as a template to signal
188
+ # we are done.
189
+ if !wait_for_template
190
+ if opts[:no_delta] != true
191
+ config = opts[:config] || {}
192
+ config = vm.update_spec_add_delta_disk_layer_on_all_disks(config)
193
+ # XXX: Should we add a version that does retries?
194
+ vm.ReconfigVM_Task(:spec => config).wait_for_completion
195
+ end
196
+ vm.MarkAsTemplate
197
+ end
198
+ end
199
+
200
+ # The losing thread now needs to wait for the winning thread to finish
201
+ # uploading and preparing the template
202
+ if wait_for_template
203
+ log "Template already exists, waiting for it to be ready"
204
+ vm = _wait_for_template_ready @template_folder, vm_name
205
+ log "Template fully prepared and ready to be cloned"
206
+ end
207
+
208
+ vm
209
+ end
210
+
211
+ # Looks up a template by name in the configured template_path. Should be used
212
+ # before uploading the VM via upload_ovf_as_template, although that is
213
+ # not strictly required, but a lot more efficient.
214
+ # @param template_name [String] Name of the template to be used. A cluster
215
+ # specific post-fix will automatically be added.
216
+ # @return [VIM::VirtualMachine] The template as a VIM::VirtualMachine instance
217
+ # or nil
218
+ def lookup_template template_name
219
+ template_path = "#{template_name}-#{@computer.name}"
220
+ template = @template_folder.traverse(template_path, RbVmomi::VIM::VirtualMachine)
221
+ if template
222
+ config = template.config
223
+ is_template = config && config.template
224
+ if !is_template
225
+ template = nil
226
+ end
227
+ end
228
+ template
229
+ end
230
+
231
+ # Creates a linked clone of a template prepared with upload_ovf_as_template.
232
+ # The function waits for completion on the clone task. Optionally, in case
233
+ # two level templates are being used, this function can wait for another
234
+ # thread to finish creating the second level template. See class comments
235
+ # for the concept of multi level templates.
236
+ # @param template_name [String] Name of the template to be used. A cluster
237
+ # specific post-fix will automatically be added.
238
+ # @param vm_name [String] Name of the new VM that is being created via cloning.
239
+ # @param config [Hash] VM Config delta to apply after the VM is cloned.
240
+ # Allows the template to be customized, e.g. to adjust
241
+ # CPU or Memory sizes or set annotations.
242
+ # @option opts [int] :is_template If true, the clone is assumed to be a template
243
+ # again and collision and de-duping logic kicks
244
+ # in.
245
+ # @return [VIM::VirtualMachine] The VIM::VirtualMachine instance of the clone
246
+ def linked_clone template_vm, vm_name, config, opts = {}
247
+ spec = {
248
+ location: {
249
+ pool: @rp,
250
+ datastore: @datastore,
251
+ diskMoveType: :moveChildMostDiskBacking,
252
+ },
253
+ powerOn: false,
254
+ template: false,
255
+ config: config,
256
+ }
257
+ if opts[:is_template]
258
+ wait_for_template = false
259
+ template_name = "#{vm_name}-#{@computer.name}"
260
+ begin
261
+ vm = template_vm.CloneVM_Task(
262
+ folder: @template_folder,
263
+ name: template_name,
264
+ spec: spec
265
+ ).wait_for_completion
266
+ rescue RbVmomi::Fault => fault
267
+ if fault.fault.is_a?(RbVmomi::VIM::DuplicateName)
268
+ wait_for_template = true
269
+ else
270
+ raise
271
+ end
272
+ end
273
+
274
+ if wait_for_template
275
+ puts "#{Time.now}: Template already exists, waiting for it to be ready"
276
+ vm = _wait_for_template_ready @template_folder, template_name
277
+ puts "#{Time.now}: Template ready"
278
+ end
279
+ else
280
+ vm = template_vm.CloneVM_Task(
281
+ folder: @vmfolder,
282
+ name: vm_name,
283
+ spec: spec
284
+ ).wait_for_completion
285
+ end
286
+ vm
287
+ end
288
+
289
+ # Internal helper method that waits for a template to be fully created. It
290
+ # polls until it finds the VM in the inventory, and once it is there, waits
291
+ # for it to be fully created and marked as a template. This function will
292
+ # block for forever if the template never gets created or marked as a
293
+ # template.
294
+ # @param vm_folder [VIM::Folder] Folder in which we expect the template to show up
295
+ # @param vm_name [String] Name of the VM we are waiting for
296
+ # @return [VIM::VirtualMachine] The VM we were waiting for when it is ready
297
+ def _wait_for_template_ready vm_folder, vm_name
298
+ vm = nil
299
+ while !vm
300
+ sleep 3
301
+ # XXX: Optimize this
302
+ vm = vm_folder.children.find{|x| x.name == vm_name}
303
+ end
304
+ log "Template VM found"
305
+ sleep 2
306
+ while true
307
+ runtime, template = vm.collect 'runtime', 'config.template'
308
+ ready = runtime && runtime.host && runtime.powerState == "poweredOff"
309
+ ready = ready && template
310
+ if ready
311
+ break
312
+ end
313
+ sleep 5
314
+ end
315
+
316
+ vm
317
+ end
318
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright (c) 2012-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ require 'yaml'
5
+
6
+ # A class to manage VM leases
7
+ #
8
+ # This class uses YAML encoded VM annotations (config.annotation) to manage a
9
+ # lease system. It helps add such lease info onto new and existing VMs and to
10
+ # find VMs that have expired leases or that are about to have expired leases.
11
+ # The calling code can use those to generate emails with about-to-expire
12
+ # notifications, suspend, power off or destroy VMs that have exceeded their
13
+ # lease, etc.
14
+ class LeaseTool
15
+ # Lists of VM properties the LeaseTool needs to do its job. Can be used to
16
+ # construct larger property collector calls that retrieve more info than just
17
+ # one subsystem needs.
18
+ # @return [Array] List of property names
19
+ def vms_props_list
20
+ ['name', 'config.annotation']
21
+ end
22
+
23
+ # Fetch all VM properties that the LeaseTool needs on all VMs passed in.
24
+ # @param vms [Array] List of VIM::VirtualMachine instances
25
+ # @return [Hash] Hash of VMs as keys and their properties as values
26
+ def get_vms_props vms
27
+ out = {}
28
+ if vms.length > 0
29
+ pc = vms.first._connection.serviceContent.propertyCollector
30
+ out = pc.collectMultiple(vms, 'name', 'config.annotation')
31
+ end
32
+ out
33
+ end
34
+
35
+ # Retrieve the current time as used by the lease tool.
36
+ # @return [Time] Current time as used by the lease tool
37
+ def current_time
38
+ # XXX: Should swith to time provided by VC
39
+ Time.now
40
+ end
41
+
42
+ # Helper function that sets the lease info in a passed in VM config. If there
43
+ # is no annotation, it is added. If there is an annotation, it is updated to
44
+ # include the lease info. Note that if the annotation isn't YAML, it is
45
+ # overwritten.
46
+ # @param vmconfig [Hash] Virtual Machine config spec
47
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
48
+ # @return [Hash] Updated Virtual Machine config spec
49
+ def set_lease_in_vm_config vmconfig, lease_minutes
50
+ annotation = vmconfig[:annotation]
51
+ annotation ||= ""
52
+ note = YAML.load annotation
53
+ if !note.is_a?(Hash)
54
+ note = {}
55
+ end
56
+ lease = current_time + lease_minutes * 60
57
+ note['lease'] = lease
58
+ vmconfig[:annotation] = YAML.dump(note)
59
+ vmconfig
60
+ end
61
+
62
+ # Issue ReconfigVM_Task on the VM to update the lease. User can pass in current
63
+ # annotation, but if not, it is retrieved on demand. A task is returned, i.e.
64
+ # function doesn't wait for completion.
65
+ # @param vm [VIM::VirtualMachine] Virtual Machine instance
66
+ # @param lease_minutes [int] Time to lease expiration from now in minutes
67
+ # @param annotation [String] 'config.annotation' property of the VM. Optional.
68
+ # @return [VIM::Task] VM reconfiguration task
69
+ def set_lease_on_vm_task vm, lease_minutes, annotation = nil
70
+ if !annotation
71
+ annotation = vm.collect 'config.annotation'
72
+ end
73
+ vmconfig = {:annotation => annotation}
74
+ vmconfig = set_lease_in_vm_config vmconfig, lease_minutes
75
+ # XXX: It may be a good idea to cite the VM version here to avoid
76
+ # concurrent writes to the annotation stepping on each others toes
77
+ vm.ReconfigVM_Task(:spec => vmconfig)
78
+ end
79
+
80
+ # Issue ReconfigVM_Task to set the lease on all VMs that currently do not
81
+ # have a lease. All VM reconfigurations are done in parallel and the function
82
+ # waits for all of them to complete
83
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
84
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
85
+ # @option opts [int] :lease_minutes Time to lease expiration from now in minutes
86
+ # @return [Array] List of previously leaseless VMs that now have a lease
87
+ def set_lease_on_leaseless_vms vms, vmprops, opts = {}
88
+ lease_minutes = opts[:lease_minutes]
89
+ if !lease_minutes
90
+ raise "Expected lease_minutes to be specified"
91
+ end
92
+ vms = find_leaseless_vms vms, vmprops
93
+ if vms.length > 0
94
+ tasks = vms.map do |vm|
95
+ annotation = vmprops[vm]['config.annotation']
96
+ task = set_lease_on_vm_task(vm, lease_minutes, annotation)
97
+ task
98
+ end
99
+ si = vms.first._connection.serviceInstance
100
+ si.wait_for_multiple_tasks [], tasks
101
+ end
102
+ vms
103
+ end
104
+
105
+ # Filter the list of passed in Virtual Machines and find the ones that currently
106
+ # do not have a lease.
107
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
108
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
109
+ # @return [Array] List of leaseless VMs
110
+ def find_leaseless_vms vms, vmprops
111
+ vms.reject do |vm|
112
+ props = vmprops[vm]
113
+ annotation = props['config.annotation']
114
+ if annotation
115
+ note = YAML.load annotation
116
+ note.is_a?(Hash) && note['lease']
117
+ end
118
+ end
119
+ end
120
+
121
+ # Filter the list of passed in Virtul Machines and find the one that are
122
+ # expired. A time offset can be used to identify VMs that will expire at
123
+ # a certain point in the future.
124
+ # If a VM doesn't have a lease, it is treated as never expiring.
125
+ # @param vms [Array] List of VIM::VirtualMachine instances, may or may not have leases
126
+ # @param vmprops [Hash] Hash of VIM::VirtualMachine instances to their properties
127
+ # @option opts [int] :time_delta Time delta (seconds) to be added to current time
128
+ # @return [Array] List of expired VMs
129
+ def filter_expired_vms vms, vmprops, opts = {}
130
+ time_delta = opts[:time_delta] || 0
131
+ time = current_time + time_delta
132
+
133
+ out = vms.map do |vm|
134
+ props = vmprops[vm]
135
+ next unless annotation = props['config.annotation']
136
+ note = YAML.load annotation
137
+ next unless note.is_a?(Hash) && lease = note['lease']
138
+ next unless time > lease
139
+ time_to_expiration = ((lease - time) + time_delta)
140
+ [vm, time_to_expiration]
141
+ end.compact
142
+ out = Hash[out]
143
+ out
144
+ end
145
+ end