rbvmomi2 3.0.0

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