rbvmomi 1.0.2 → 1.1.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.
- data/.yardopts +5 -0
 - data/README.rdoc +71 -0
 - data/Rakefile +11 -0
 - data/VERSION +1 -1
 - data/devel/analyze-xml.rb +29 -29
 - data/{test/test.rb → examples/create_vm.rb} +40 -15
 - data/examples/extraConfig.rb +54 -0
 - data/examples/power.rb +57 -0
 - data/examples/readme-1.rb +35 -0
 - data/examples/readme-2.rb +51 -0
 - data/lib/rbvmomi.rb +3 -282
 - data/lib/rbvmomi/{types.rb → basic_types.rb} +37 -97
 - data/lib/rbvmomi/connection.rb +239 -0
 - data/lib/rbvmomi/fault.rb +17 -0
 - data/lib/{trivial_soap.rb → rbvmomi/trivial_soap.rb} +31 -13
 - data/lib/rbvmomi/trollop.rb +6 -2
 - data/lib/rbvmomi/type_loader.rb +72 -0
 - data/lib/rbvmomi/vim.rb +76 -0
 - data/lib/rbvmomi/vim/ComputeResource.rb +51 -0
 - data/lib/rbvmomi/vim/Datacenter.rb +17 -0
 - data/lib/rbvmomi/vim/Datastore.rb +68 -0
 - data/lib/rbvmomi/vim/Folder.rb +112 -0
 - data/lib/rbvmomi/vim/ManagedEntity.rb +46 -0
 - data/lib/rbvmomi/vim/ManagedObject.rb +55 -0
 - data/lib/rbvmomi/vim/ObjectContent.rb +23 -0
 - data/lib/rbvmomi/vim/ObjectUpdate.rb +23 -0
 - data/lib/rbvmomi/vim/OvfManager.rb +93 -0
 - data/lib/rbvmomi/vim/ResourcePool.rb +18 -0
 - data/lib/rbvmomi/vim/ServiceInstance.rb +53 -0
 - data/lib/rbvmomi/vim/Task.rb +31 -0
 - data/lib/rbvmomi/vim/VirtualMachine.rb +7 -0
 - data/test/test_deserialization.rb +11 -55
 - data/test/test_emit_request.rb +13 -10
 - data/test/test_exceptions.rb +16 -0
 - data/test/test_parse_response.rb +2 -10
 - data/test/test_serialization.rb +14 -11
 - metadata +41 -25
 - data/.gitignore +0 -4
 - data/README.md +0 -12
 - data/lib/rbvmomi/extensions.rb +0 -491
 - data/lib/rbvmomi/profile.rb +0 -22
 - data/test/runner.rb +0 -3
 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::ComputeResource
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Aggregate cluster information.
         
     | 
| 
      
 3 
     | 
    
         
            +
              #
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @note Values are returned in a hash.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @return [Mhz] totalCPU: Sum of the frequencies of each CPU in the cluster.
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @return [Mhz] usedCPU: CPU cycles used across the cluster.
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @return [MB] totalMem: Total RAM.
         
     | 
| 
      
 9 
     | 
    
         
            +
              # @return [MB] usedMem: Used RAM.
         
     | 
| 
      
 10 
     | 
    
         
            +
              def stats
         
     | 
| 
      
 11 
     | 
    
         
            +
                filterSpec = RbVmomi::VIM.PropertyFilterSpec(
         
     | 
| 
      
 12 
     | 
    
         
            +
                  objectSet: [{
         
     | 
| 
      
 13 
     | 
    
         
            +
                    obj: self,
         
     | 
| 
      
 14 
     | 
    
         
            +
                    selectSet: [
         
     | 
| 
      
 15 
     | 
    
         
            +
                      RbVmomi::VIM.TraversalSpec(
         
     | 
| 
      
 16 
     | 
    
         
            +
                        name: 'tsHosts',
         
     | 
| 
      
 17 
     | 
    
         
            +
                        type: 'ComputeResource',
         
     | 
| 
      
 18 
     | 
    
         
            +
                        path: 'host',
         
     | 
| 
      
 19 
     | 
    
         
            +
                        skip: false,
         
     | 
| 
      
 20 
     | 
    
         
            +
                      )
         
     | 
| 
      
 21 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  }],
         
     | 
| 
      
 23 
     | 
    
         
            +
                  propSet: [{
         
     | 
| 
      
 24 
     | 
    
         
            +
                    pathSet: %w(name overallStatus summary.hardware.cpuMhz
         
     | 
| 
      
 25 
     | 
    
         
            +
                                summary.hardware.numCpuCores summary.hardware.memorySize
         
     | 
| 
      
 26 
     | 
    
         
            +
                                summary.quickStats.overallCpuUsage
         
     | 
| 
      
 27 
     | 
    
         
            +
                                summary.quickStats.overallMemoryUsage),
         
     | 
| 
      
 28 
     | 
    
         
            +
                    type: 'HostSystem'
         
     | 
| 
      
 29 
     | 
    
         
            +
                  }]
         
     | 
| 
      
 30 
     | 
    
         
            +
                )
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                stats = {
         
     | 
| 
      
 35 
     | 
    
         
            +
                  totalCPU: 0,
         
     | 
| 
      
 36 
     | 
    
         
            +
                  totalMem: 0,
         
     | 
| 
      
 37 
     | 
    
         
            +
                  usedCPU: 0,
         
     | 
| 
      
 38 
     | 
    
         
            +
                  usedMem: 0,
         
     | 
| 
      
 39 
     | 
    
         
            +
                }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                result.each do |x|
         
     | 
| 
      
 42 
     | 
    
         
            +
                  next if x['overallStatus'] == 'red'
         
     | 
| 
      
 43 
     | 
    
         
            +
                  stats[:totalCPU] += x['summary.hardware.cpuMhz'] * x['summary.hardware.numCpuCores']
         
     | 
| 
      
 44 
     | 
    
         
            +
                  stats[:totalMem] += x['summary.hardware.memorySize'] / (1024*1024)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  stats[:usedCPU] += x['summary.quickStats.overallCpuUsage'] || 0
         
     | 
| 
      
 46 
     | 
    
         
            +
                  stats[:usedMem] += x['summary.quickStats.overallMemoryUsage'] || 0
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                stats
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::Datacenter
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Traverse the given inventory +path+ to find a ComputeResource.
         
     | 
| 
      
 3 
     | 
    
         
            +
              def find_compute_resource path
         
     | 
| 
      
 4 
     | 
    
         
            +
                hostFolder.traverse path, RbVmomi::VIM::ComputeResource
         
     | 
| 
      
 5 
     | 
    
         
            +
              end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # Find the Datastore with the given +name+.
         
     | 
| 
      
 8 
     | 
    
         
            +
              def find_datastore name
         
     | 
| 
      
 9 
     | 
    
         
            +
                datastore.find { |x| x.name == name }
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              # Traverse the given inventory +path+ to find a VirtualMachine.
         
     | 
| 
      
 13 
     | 
    
         
            +
              def find_vm path
         
     | 
| 
      
 14 
     | 
    
         
            +
                vmFolder.traverse path, RbVmomi::VIM::VirtualMachine
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @note +download+ and +upload+ require +curl+. If +curl+ is not in your +PATH+
         
     | 
| 
      
 2 
     | 
    
         
            +
            #       then set the +CURL+ environment variable to point to it.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # @todo Use an HTTP library instead of executing +curl+.
         
     | 
| 
      
 4 
     | 
    
         
            +
            class RbVmomi::VIM::Datastore
         
     | 
| 
      
 5 
     | 
    
         
            +
              CURLBIN = ENV['CURL'] || "curl" #@private
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # Check whether a file exists on this datastore.
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @param path [String] Path on the datastore.
         
     | 
| 
      
 9 
     | 
    
         
            +
              def exists? path
         
     | 
| 
      
 10 
     | 
    
         
            +
                req = Net::HTTP::Head.new mkuripath(path)
         
     | 
| 
      
 11 
     | 
    
         
            +
                req.initialize_http_header 'cookie' => @soap.cookie
         
     | 
| 
      
 12 
     | 
    
         
            +
                resp = @soap.http.request req
         
     | 
| 
      
 13 
     | 
    
         
            +
                case resp
         
     | 
| 
      
 14 
     | 
    
         
            +
                when Net::HTTPSuccess
         
     | 
| 
      
 15 
     | 
    
         
            +
                  true
         
     | 
| 
      
 16 
     | 
    
         
            +
                when Net::HTTPNotFound
         
     | 
| 
      
 17 
     | 
    
         
            +
                  false
         
     | 
| 
      
 18 
     | 
    
         
            +
                else
         
     | 
| 
      
 19 
     | 
    
         
            +
                  fail resp.inspect
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              # Download a file from this datastore.
         
     | 
| 
      
 24 
     | 
    
         
            +
              # @param remote_path [String] Source path on the datastore.
         
     | 
| 
      
 25 
     | 
    
         
            +
              # @param local_path [String] Destination path on the local machine.
         
     | 
| 
      
 26 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 27 
     | 
    
         
            +
              def download remote_path, local_path
         
     | 
| 
      
 28 
     | 
    
         
            +
                url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
         
     | 
| 
      
 29 
     | 
    
         
            +
                pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
         
     | 
| 
      
 30 
     | 
    
         
            +
                            "-o", local_path,
         
     | 
| 
      
 31 
     | 
    
         
            +
                            "-b", @soap.cookie,
         
     | 
| 
      
 32 
     | 
    
         
            +
                            url,
         
     | 
| 
      
 33 
     | 
    
         
            +
                            out: '/dev/null'
         
     | 
| 
      
 34 
     | 
    
         
            +
                Process.waitpid(pid, 0)
         
     | 
| 
      
 35 
     | 
    
         
            +
                fail "download failed" unless $?.success?
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              # Upload a file to this datastore.
         
     | 
| 
      
 39 
     | 
    
         
            +
              # @param remote_path [String] Destination path on the datastore.
         
     | 
| 
      
 40 
     | 
    
         
            +
              # @param local_path [String] Source path on the local machine.
         
     | 
| 
      
 41 
     | 
    
         
            +
              # @return [void]
         
     | 
| 
      
 42 
     | 
    
         
            +
              def upload remote_path, local_path
         
     | 
| 
      
 43 
     | 
    
         
            +
                url = "http#{@soap.http.use_ssl? ? 's' : ''}://#{@soap.http.address}:#{@soap.http.port}#{mkuripath(remote_path)}"
         
     | 
| 
      
 44 
     | 
    
         
            +
                pid = spawn CURLBIN, "-k", '--noproxy', '*', '-f',
         
     | 
| 
      
 45 
     | 
    
         
            +
                            "-T", local_path,
         
     | 
| 
      
 46 
     | 
    
         
            +
                            "-b", @soap.cookie,
         
     | 
| 
      
 47 
     | 
    
         
            +
                            url,
         
     | 
| 
      
 48 
     | 
    
         
            +
                            out: '/dev/null'
         
     | 
| 
      
 49 
     | 
    
         
            +
                Process.waitpid(pid, 0)
         
     | 
| 
      
 50 
     | 
    
         
            +
                fail "upload failed" unless $?.success?
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              private
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              def datacenter
         
     | 
| 
      
 56 
     | 
    
         
            +
                return @datacenter if @datacenter
         
     | 
| 
      
 57 
     | 
    
         
            +
                x = parent
         
     | 
| 
      
 58 
     | 
    
         
            +
                while not x.is_a? Datacenter
         
     | 
| 
      
 59 
     | 
    
         
            +
                  x = x.parent
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
                fail unless x.is_a? Datacenter
         
     | 
| 
      
 62 
     | 
    
         
            +
                @datacenter = x
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              def mkuripath path
         
     | 
| 
      
 66 
     | 
    
         
            +
                "/folder/#{URI.escape path}?dcPath=#{URI.escape datacenter.name}&dsName=#{URI.escape name}"
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,112 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::Folder
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Retrieve a child entity
         
     | 
| 
      
 3 
     | 
    
         
            +
              # @param name [String] Name of the child.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @param type [Class] Return nil unless the found entity <tt>is_a? type</tt>.
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @return [VIM::ManagedEntity]
         
     | 
| 
      
 6 
     | 
    
         
            +
              def find name, type=Object
         
     | 
| 
      
 7 
     | 
    
         
            +
                x = @soap.searchIndex.FindChild(entity: self, name: name)
         
     | 
| 
      
 8 
     | 
    
         
            +
                x if x.is_a? type
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              # Alias to <tt>traverse path, type, true</tt>
         
     | 
| 
      
 12 
     | 
    
         
            +
              # @see #traverse
         
     | 
| 
      
 13 
     | 
    
         
            +
              def traverse! path, type=Object
         
     | 
| 
      
 14 
     | 
    
         
            +
                traverse path, type, true
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              # Retrieve a descendant of this Folder.
         
     | 
| 
      
 18 
     | 
    
         
            +
              # @param path [String] Path delimited by '/'.
         
     | 
| 
      
 19 
     | 
    
         
            +
              # @param type (see Folder#find)
         
     | 
| 
      
 20 
     | 
    
         
            +
              # @param create [Boolean] If set, create folders that don't exist.
         
     | 
| 
      
 21 
     | 
    
         
            +
              # @return (see Folder#find)
         
     | 
| 
      
 22 
     | 
    
         
            +
              # @todo Move +create+ functionality into another method.
         
     | 
| 
      
 23 
     | 
    
         
            +
              def traverse path, type=Object, create=false
         
     | 
| 
      
 24 
     | 
    
         
            +
                es = path.split('/').reject(&:empty?)
         
     | 
| 
      
 25 
     | 
    
         
            +
                return self if es.empty?
         
     | 
| 
      
 26 
     | 
    
         
            +
                final = es.pop
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                p = es.inject(self) do |f,e|
         
     | 
| 
      
 29 
     | 
    
         
            +
                  f.find(e, Folder) || (create && f.CreateFolder(name: e)) || return
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                if x = p.find(final, type)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  x
         
     | 
| 
      
 34 
     | 
    
         
            +
                elsif create and type == Folder
         
     | 
| 
      
 35 
     | 
    
         
            +
                  p.CreateFolder(name: final)
         
     | 
| 
      
 36 
     | 
    
         
            +
                else
         
     | 
| 
      
 37 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              # Alias to +childEntity+.
         
     | 
| 
      
 42 
     | 
    
         
            +
              def children
         
     | 
| 
      
 43 
     | 
    
         
            +
                childEntity
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              # Efficiently retrieve properties from descendants of this folder.
         
     | 
| 
      
 47 
     | 
    
         
            +
              #
         
     | 
| 
      
 48 
     | 
    
         
            +
              # @param propSpecs [Hash] Specification of which properties to retrieve from
         
     | 
| 
      
 49 
     | 
    
         
            +
              #                         which entities. Keys may be symbols, strings, or
         
     | 
| 
      
 50 
     | 
    
         
            +
              #                         classes identifying ManagedEntity subtypes to be
         
     | 
| 
      
 51 
     | 
    
         
            +
              #                         included in the results. Values are an array of
         
     | 
| 
      
 52 
     | 
    
         
            +
              #                         property paths (strings) or the symbol :all.
         
     | 
| 
      
 53 
     | 
    
         
            +
              #
         
     | 
| 
      
 54 
     | 
    
         
            +
              # @return [Hash] Tree of inventory items. Folders are hashes from child name
         
     | 
| 
      
 55 
     | 
    
         
            +
              #                to child result. Objects are hashes from property path to
         
     | 
| 
      
 56 
     | 
    
         
            +
              #                value.
         
     | 
| 
      
 57 
     | 
    
         
            +
              #
         
     | 
| 
      
 58 
     | 
    
         
            +
              # @todo Return ObjectContent instead of the leaf hash.
         
     | 
| 
      
 59 
     | 
    
         
            +
              def inventory propSpecs={}
         
     | 
| 
      
 60 
     | 
    
         
            +
                propSet = [{ type: 'Folder', pathSet: ['name', 'parent'] }]
         
     | 
| 
      
 61 
     | 
    
         
            +
                propSpecs.each do |k,v|
         
     | 
| 
      
 62 
     | 
    
         
            +
                  case k
         
     | 
| 
      
 63 
     | 
    
         
            +
                  when RbVmomi::VIM::ManagedEntity
         
     | 
| 
      
 64 
     | 
    
         
            +
                    k = k.wsdl_name
         
     | 
| 
      
 65 
     | 
    
         
            +
                  when Symbol, String
         
     | 
| 
      
 66 
     | 
    
         
            +
                    k = k.to_s
         
     | 
| 
      
 67 
     | 
    
         
            +
                  else
         
     | 
| 
      
 68 
     | 
    
         
            +
                    fail "key must be a ManagedEntity"
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  h = { type: k }
         
     | 
| 
      
 72 
     | 
    
         
            +
                  if v == :all
         
     | 
| 
      
 73 
     | 
    
         
            +
                    h[:all] = true
         
     | 
| 
      
 74 
     | 
    
         
            +
                  elsif v.is_a? Array
         
     | 
| 
      
 75 
     | 
    
         
            +
                    h[:pathSet] = v + %w(parent)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  else
         
     | 
| 
      
 77 
     | 
    
         
            +
                    fail "value must be an array of property paths or :all"
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  propSet << h
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                filterSpec = RbVmomi::VIM.PropertyFilterSpec(
         
     | 
| 
      
 83 
     | 
    
         
            +
                  objectSet: [
         
     | 
| 
      
 84 
     | 
    
         
            +
                    obj: self,
         
     | 
| 
      
 85 
     | 
    
         
            +
                    selectSet: [
         
     | 
| 
      
 86 
     | 
    
         
            +
                      RbVmomi::VIM.TraversalSpec(
         
     | 
| 
      
 87 
     | 
    
         
            +
                        name: 'tsFolder',
         
     | 
| 
      
 88 
     | 
    
         
            +
                        type: 'Folder',
         
     | 
| 
      
 89 
     | 
    
         
            +
                        path: 'childEntity',
         
     | 
| 
      
 90 
     | 
    
         
            +
                        skip: false,
         
     | 
| 
      
 91 
     | 
    
         
            +
                        selectSet: [
         
     | 
| 
      
 92 
     | 
    
         
            +
                          RbVmomi::VIM.SelectionSpec(name: 'tsFolder')
         
     | 
| 
      
 93 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 94 
     | 
    
         
            +
                      )
         
     | 
| 
      
 95 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 96 
     | 
    
         
            +
                  ],
         
     | 
| 
      
 97 
     | 
    
         
            +
                  propSet: propSet
         
     | 
| 
      
 98 
     | 
    
         
            +
                )
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                tree = { self => {} }
         
     | 
| 
      
 103 
     | 
    
         
            +
                result.each do |x|
         
     | 
| 
      
 104 
     | 
    
         
            +
                  obj = x.obj
         
     | 
| 
      
 105 
     | 
    
         
            +
                  next if obj == self
         
     | 
| 
      
 106 
     | 
    
         
            +
                  h = Hash[x.propSet.map { |y| [y.name, y.val] }]
         
     | 
| 
      
 107 
     | 
    
         
            +
                  tree[h['parent']][h['name']] = [obj, h]
         
     | 
| 
      
 108 
     | 
    
         
            +
                  tree[obj] = {} if obj.is_a? RbVmomi::VIM::Folder
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
                tree
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::ManagedEntity
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Retrieve the ancestors of the entity.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # @return [Array] Ancestors of this entity, starting with the root.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def path
         
     | 
| 
      
 5 
     | 
    
         
            +
                filterSpec = RbVmomi::VIM.PropertyFilterSpec(
         
     | 
| 
      
 6 
     | 
    
         
            +
                  objectSet: [{
         
     | 
| 
      
 7 
     | 
    
         
            +
                    obj: self,
         
     | 
| 
      
 8 
     | 
    
         
            +
                    selectSet: [
         
     | 
| 
      
 9 
     | 
    
         
            +
                      RbVmomi::VIM.TraversalSpec(
         
     | 
| 
      
 10 
     | 
    
         
            +
                        name: 'tsME',
         
     | 
| 
      
 11 
     | 
    
         
            +
                        type: 'ManagedEntity',
         
     | 
| 
      
 12 
     | 
    
         
            +
                        path: 'parent',
         
     | 
| 
      
 13 
     | 
    
         
            +
                        skip: false,
         
     | 
| 
      
 14 
     | 
    
         
            +
                        selectSet: [
         
     | 
| 
      
 15 
     | 
    
         
            +
                          RbVmomi::VIM.SelectionSpec(name: 'tsME')
         
     | 
| 
      
 16 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 17 
     | 
    
         
            +
                      )
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  }],
         
     | 
| 
      
 20 
     | 
    
         
            +
                  propSet: [{
         
     | 
| 
      
 21 
     | 
    
         
            +
                    pathSet: %w(name parent),
         
     | 
| 
      
 22 
     | 
    
         
            +
                    type: 'ManagedEntity'
         
     | 
| 
      
 23 
     | 
    
         
            +
                  }]
         
     | 
| 
      
 24 
     | 
    
         
            +
                )
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                result = @soap.propertyCollector.RetrieveProperties(specSet: [filterSpec])
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                tree = {}
         
     | 
| 
      
 29 
     | 
    
         
            +
                result.each { |x| tree[x.obj] = [x['parent'], x['name']] }
         
     | 
| 
      
 30 
     | 
    
         
            +
                a = []
         
     | 
| 
      
 31 
     | 
    
         
            +
                cur = self
         
     | 
| 
      
 32 
     | 
    
         
            +
                while cur
         
     | 
| 
      
 33 
     | 
    
         
            +
                  parent, name = *tree[cur]
         
     | 
| 
      
 34 
     | 
    
         
            +
                  a << [cur, name]
         
     | 
| 
      
 35 
     | 
    
         
            +
                  cur = parent
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                a.reverse
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              # Return a string representation of +path+ suitable for display.
         
     | 
| 
      
 41 
     | 
    
         
            +
              # @return [String]
         
     | 
| 
      
 42 
     | 
    
         
            +
              # @see #path
         
     | 
| 
      
 43 
     | 
    
         
            +
              def pretty_path
         
     | 
| 
      
 44 
     | 
    
         
            +
                path[1..-1].map { |x| x[1] } * '/'
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::ManagedObject
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Wait for updates on an object until a condition becomes true.
         
     | 
| 
      
 3 
     | 
    
         
            +
              #
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @param pathSet [Array] Property paths to wait for updates to.
         
     | 
| 
      
 5 
     | 
    
         
            +
              # @yield Called when an update to a subscribed property occurs.
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @yieldreturn [Boolean] Whether to stop waiting.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @todo Pass the current property values to the block.
         
     | 
| 
      
 9 
     | 
    
         
            +
              def wait_until *pathSet, &b
         
     | 
| 
      
 10 
     | 
    
         
            +
                all = pathSet.empty?
         
     | 
| 
      
 11 
     | 
    
         
            +
                filter = @soap.propertyCollector.CreateFilter :spec => {
         
     | 
| 
      
 12 
     | 
    
         
            +
                  :propSet => [{ :type => self.class.wsdl_name, :all => all, :pathSet => pathSet }],
         
     | 
| 
      
 13 
     | 
    
         
            +
                  :objectSet => [{ :obj => self }],
         
     | 
| 
      
 14 
     | 
    
         
            +
                }, :partialUpdates => false
         
     | 
| 
      
 15 
     | 
    
         
            +
                ver = ''
         
     | 
| 
      
 16 
     | 
    
         
            +
                loop do
         
     | 
| 
      
 17 
     | 
    
         
            +
                  result = @soap.propertyCollector.WaitForUpdates(version: ver)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ver = result.version
         
     | 
| 
      
 19 
     | 
    
         
            +
                  if x = b.call
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return x
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              ensure
         
     | 
| 
      
 24 
     | 
    
         
            +
                filter.DestroyPropertyFilter if filter
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              # Efficiently retrieve multiple properties from an object.
         
     | 
| 
      
 28 
     | 
    
         
            +
              # @param pathSet [Array] Properties to return.
         
     | 
| 
      
 29 
     | 
    
         
            +
              # @return [Hash] Hash from property paths to values.
         
     | 
| 
      
 30 
     | 
    
         
            +
              def collect! *pathSet
         
     | 
| 
      
 31 
     | 
    
         
            +
                spec = {
         
     | 
| 
      
 32 
     | 
    
         
            +
                  objectSet: [{ obj: self }],
         
     | 
| 
      
 33 
     | 
    
         
            +
                  propSet: [{
         
     | 
| 
      
 34 
     | 
    
         
            +
                    pathSet: pathSet,
         
     | 
| 
      
 35 
     | 
    
         
            +
                    type: self.class.wsdl_name
         
     | 
| 
      
 36 
     | 
    
         
            +
                  }]
         
     | 
| 
      
 37 
     | 
    
         
            +
                }
         
     | 
| 
      
 38 
     | 
    
         
            +
                @soap.propertyCollector.RetrieveProperties(specSet: [spec])[0].to_hash
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              # Efficiently retrieve multiple properties from an object.
         
     | 
| 
      
 42 
     | 
    
         
            +
              # @param pathSet (see #collect!)
         
     | 
| 
      
 43 
     | 
    
         
            +
              # @yield [*values] Property values in same order as +pathSet+.
         
     | 
| 
      
 44 
     | 
    
         
            +
              # @return [Array] Property values in same order as +pathSet+, or the return
         
     | 
| 
      
 45 
     | 
    
         
            +
              #                 value from the block if it is given.
         
     | 
| 
      
 46 
     | 
    
         
            +
              def collect *pathSet
         
     | 
| 
      
 47 
     | 
    
         
            +
                h = collect! *pathSet
         
     | 
| 
      
 48 
     | 
    
         
            +
                a = pathSet.map { |k| h[k.to_s] }
         
     | 
| 
      
 49 
     | 
    
         
            +
                if block_given?
         
     | 
| 
      
 50 
     | 
    
         
            +
                  yield a
         
     | 
| 
      
 51 
     | 
    
         
            +
                else
         
     | 
| 
      
 52 
     | 
    
         
            +
                  a
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::ObjectContent
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Represent this ObjectContent as a hash.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # @return [Hash] A hash from property paths to values.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def to_hash
         
     | 
| 
      
 5 
     | 
    
         
            +
                @cached_hash ||= to_hash_uncached
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              # Alias for +to_hash[k]+.
         
     | 
| 
      
 9 
     | 
    
         
            +
              def [](k)
         
     | 
| 
      
 10 
     | 
    
         
            +
                to_hash[k]
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              private
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def to_hash_uncached
         
     | 
| 
      
 16 
     | 
    
         
            +
                h = {}
         
     | 
| 
      
 17 
     | 
    
         
            +
                propSet.each do |x|
         
     | 
| 
      
 18 
     | 
    
         
            +
                  fail if h.member? x.name
         
     | 
| 
      
 19 
     | 
    
         
            +
                  h[x.name] = x.val
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
                h
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RbVmomi::VIM::ObjectUpdate
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Represent this ObjectUpdate as a hash.
         
     | 
| 
      
 3 
     | 
    
         
            +
              # @return [Hash] A hash from property paths to values.
         
     | 
| 
      
 4 
     | 
    
         
            +
              def to_hash
         
     | 
| 
      
 5 
     | 
    
         
            +
                @cached_hash ||= to_hash_uncached
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              # Alias for +to_hash[k]+.
         
     | 
| 
      
 9 
     | 
    
         
            +
              def [](k)
         
     | 
| 
      
 10 
     | 
    
         
            +
                to_hash[k]
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              private
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def to_hash_uncached
         
     | 
| 
      
 16 
     | 
    
         
            +
                h = {}
         
     | 
| 
      
 17 
     | 
    
         
            +
                changeSet.each do |x|
         
     | 
| 
      
 18 
     | 
    
         
            +
                  fail if h.member? x.name
         
     | 
| 
      
 19 
     | 
    
         
            +
                  h[x.name] = x.val
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
                h
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,93 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @note +deployOVF+ and requires +curl+. If +curl+ is not in your +PATH+
         
     | 
| 
      
 2 
     | 
    
         
            +
            #       then set the +CURL+ environment variable to point to it.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # @todo Use an HTTP library instead of executing +curl+.
         
     | 
| 
      
 4 
     | 
    
         
            +
            class RbVmomi::VIM::OvfManager
         
     | 
| 
      
 5 
     | 
    
         
            +
              CURLBIN = ENV['CURL'] || "curl" #@private
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # Deploy an OVF.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              # @param [Hash] opts The options hash.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @option opts [String]             :uri Location of the OVF.
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @option opts [String]             :vmName Name of the new VM.
         
     | 
| 
      
 12 
     | 
    
         
            +
              # @option opts [VIM::Folder]        :vmFolder Folder to place the VM in.
         
     | 
| 
      
 13 
     | 
    
         
            +
              # @option opts [VIM::HostSystem]    :host Host to use.
         
     | 
| 
      
 14 
     | 
    
         
            +
              # @option opts [VIM::ResourcePool]  :resourcePool Resource pool to use.
         
     | 
| 
      
 15 
     | 
    
         
            +
              # @option opts [VIM::Datastore]     :datastore Datastore to use.
         
     | 
| 
      
 16 
     | 
    
         
            +
              # @option opts [String]             :diskProvisioning (thin) Disk provisioning mode.
         
     | 
| 
      
 17 
     | 
    
         
            +
              # @option opts [Hash]               :networkMappings Network mappings.
         
     | 
| 
      
 18 
     | 
    
         
            +
              # @option opts [Hash]               :propertyMappings Property mappings.
         
     | 
| 
      
 19 
     | 
    
         
            +
              def deployOVF opts
         
     | 
| 
      
 20 
     | 
    
         
            +
                opts = { networkMappings: {},
         
     | 
| 
      
 21 
     | 
    
         
            +
                         propertyMappings: {},
         
     | 
| 
      
 22 
     | 
    
         
            +
                         diskProvisioning: :thin }.merge opts
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                %w(uri vmName vmFolder host resourcePool datastore).each do |k|
         
     | 
| 
      
 25 
     | 
    
         
            +
                  fail "parameter #{k} required" unless opts[k.to_sym]
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                ovfImportSpec = RbVmomi::VIM::OvfCreateImportSpecParams(
         
     | 
| 
      
 29 
     | 
    
         
            +
                  hostSystem: opts[:host],
         
     | 
| 
      
 30 
     | 
    
         
            +
                  locale: "US",
         
     | 
| 
      
 31 
     | 
    
         
            +
                  entityName: opts[:vmName],
         
     | 
| 
      
 32 
     | 
    
         
            +
                  deploymentOption: "",
         
     | 
| 
      
 33 
     | 
    
         
            +
                  networkMapping: opts[:networkMappings].map{|from, to| RbVmomi::VIM::OvfNetworkMapping(name: from, network: to)},
         
     | 
| 
      
 34 
     | 
    
         
            +
                  propertyMapping: opts[:propertyMappings].map{|key, value| RbVmomi::VIM::KeyValue(key: key, value: value)},
         
     | 
| 
      
 35 
     | 
    
         
            +
                  diskProvisioning: opts[:diskProvisioning]
         
     | 
| 
      
 36 
     | 
    
         
            +
                )
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                result = CreateImportSpec(
         
     | 
| 
      
 39 
     | 
    
         
            +
                  ovfDescriptor: open(opts[:uri]).read,
         
     | 
| 
      
 40 
     | 
    
         
            +
                  resourcePool: opts[:resourcePool],
         
     | 
| 
      
 41 
     | 
    
         
            +
                  datastore: opts[:datastore],
         
     | 
| 
      
 42 
     | 
    
         
            +
                  cisp: ovfImportSpec
         
     | 
| 
      
 43 
     | 
    
         
            +
                )
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                raise result.error[0].localizedMessage if result.error && !result.error.empty?
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                if result.warning
         
     | 
| 
      
 48 
     | 
    
         
            +
                  result.warning.each{|x| puts "OVF Warning: #{x.localizedMessage.chomp}" }
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                nfcLease = opts[:resourcePool].ImportVApp(spec: result.importSpec,
         
     | 
| 
      
 52 
     | 
    
         
            +
                                                          folder: opts[:vmFolder],
         
     | 
| 
      
 53 
     | 
    
         
            +
                                                          host: opts[:host])
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                nfcLease.wait_until(:state) { nfcLease.state != "initializing" }
         
     | 
| 
      
 56 
     | 
    
         
            +
                raise nfcLease.error if nfcLease.state == "error"
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                begin
         
     | 
| 
      
 59 
     | 
    
         
            +
                  nfcLease.HttpNfcLeaseProgress(percent: 5)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  progress = 0.0
         
     | 
| 
      
 61 
     | 
    
         
            +
                  result.fileItem.each do |fileItem|
         
     | 
| 
      
 62 
     | 
    
         
            +
                    deviceUrl = nfcLease.info.deviceUrl.find{|x| x.importKey == fileItem.deviceId}
         
     | 
| 
      
 63 
     | 
    
         
            +
                    if !deviceUrl
         
     | 
| 
      
 64 
     | 
    
         
            +
                      raise "Couldn't find deviceURL for device '#{fileItem.deviceId}'"
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    # XXX handle file:// URIs
         
     | 
| 
      
 68 
     | 
    
         
            +
                    ovfFilename = opts[:uri].to_s
         
     | 
| 
      
 69 
     | 
    
         
            +
                    tmp = ovfFilename.split(/\//)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    tmp.pop
         
     | 
| 
      
 71 
     | 
    
         
            +
                    tmp << fileItem.path
         
     | 
| 
      
 72 
     | 
    
         
            +
                    filename = tmp.join("/")
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                    method = fileItem.create ? "PUT" : "POST"
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    href = deviceUrl.url.gsub("*", opts[:host].config.network.vnic[0].spec.ip.ipAddress)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    downloadCmd = "#{CURLBIN} -L '#{filename}'"
         
     | 
| 
      
 78 
     | 
    
         
            +
                    uploadCmd = "#{CURLBIN} -X #{method} --insecure -T - -H 'Content-Type: application/x-vnd.vmware-streamVmdk' -H 'Content-Length: #{fileItem.size}' '#{href}'"
         
     | 
| 
      
 79 
     | 
    
         
            +
                    system("#{downloadCmd} | #{uploadCmd}")
         
     | 
| 
      
 80 
     | 
    
         
            +
                    progress += (95.0 / result.fileItem.length)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    nfcLease.HttpNfcLeaseProgress(percent: progress.to_i)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                  nfcLease.HttpNfcLeaseProgress(percent: 100)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  vm = nfcLease.info.entity
         
     | 
| 
      
 86 
     | 
    
         
            +
                  nfcLease.HttpNfcLeaseComplete
         
     | 
| 
      
 87 
     | 
    
         
            +
                  vm
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              rescue Exception
         
     | 
| 
      
 90 
     | 
    
         
            +
                nfcLease.HttpNfcLeaseAbort
         
     | 
| 
      
 91 
     | 
    
         
            +
                raise
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
            end
         
     |