File: OvfManager.rb

package info (click to toggle)
ruby-rbvmomi 1.8.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,756 kB
  • sloc: ruby: 5,590; sh: 36; makefile: 7
file content (200 lines) | stat: -rw-r--r-- 7,533 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# @note +deployOVF+ and requires +curl+. If +curl+ is not in your +PATH+
#       then set the +CURL+ environment variable to point to it.
# @todo Use an HTTP library instead of executing +curl+.
class RbVmomi::VIM::OvfManager
  CURLBIN = ENV['CURL'] || "curl" #@private

  # Deploy an OVF.
  #
  # @param [Hash] opts The options hash.
  # @option opts [String]             :uri Location of the OVF.
  # @option opts [String]             :vmName Name of the new VM.
  # @option opts [VIM::Folder]        :vmFolder Folder to place the VM in.
  # @option opts [VIM::HostSystem]    :host Host to use.
  # @option opts [VIM::ResourcePool]  :resourcePool Resource pool to use.
  # @option opts [VIM::Datastore]     :datastore Datastore to use.
  # @option opts [String]             :diskProvisioning (thin) Disk provisioning mode.
  # @option opts [Hash]               :networkMappings Network mappings.
  # @option opts [Hash]               :propertyMappings Property mappings.
  def deployOVF opts
    opts = { :networkMappings => {},
             :propertyMappings => {},
             :diskProvisioning => :thin }.merge opts

    %w(uri vmName vmFolder host resourcePool datastore).each do |k|
      fail "parameter #{k} required" unless opts[k.to_sym]
    end

    ovfImportSpec = RbVmomi::VIM::OvfCreateImportSpecParams(
      :hostSystem => opts[:host],
      :locale => "US",
      :entityName => opts[:vmName],
      :deploymentOption => "",
      :networkMapping => opts[:networkMappings].map{|from, to| RbVmomi::VIM::OvfNetworkMapping(:name => from, :network => to)},
      :propertyMapping => opts[:propertyMappings].to_a,
      :diskProvisioning => opts[:diskProvisioning]
    )

    result = CreateImportSpec(
      :ovfDescriptor => open(opts[:uri]).read,
      :resourcePool => opts[:resourcePool],
      :datastore => opts[:datastore],
      :cisp => ovfImportSpec
    )

    raise result.error[0].localizedMessage if result.error && !result.error.empty?

    if result.warning
      result.warning.each{|x| puts "OVF Warning: #{x.localizedMessage.chomp}" }
    end

    importSpec = result.importSpec
    if importSpec && importSpec.instantiationOst && importSpec.instantiationOst.child
      importSpec.instantiationOst.child.each do |child|
        child.section.map do |section|
          section.xml = _handle_ost(section.xml, opts)
        end
      end
    end
    
    nfcLease = opts[:resourcePool].ImportVApp(:spec => importSpec,
                                              :folder => opts[:vmFolder],
                                              :host => opts[:host])

    nfcLease.wait_until(:state) { nfcLease.state != "initializing" }
    raise nfcLease.error if nfcLease.state == "error"
    begin
      nfcLease.HttpNfcLeaseProgress(:percent => 5)
      timeout, = nfcLease.collect 'info.leaseTimeout'
      puts "DEBUG: Timeout: #{timeout}"
      if timeout < 4 * 60
        puts "WARNING: OVF upload NFC lease timeout less than 4 minutes"
      end
      progress = 5.0
      result.fileItem.each do |fileItem|
        leaseInfo, leaseState, leaseError = nfcLease.collect 'info', 'state', 'error'
        # Retry nfcLease.collect because of PR 969599:
        # If retrying property collector works, this means there is a network
        # or VC overloading problem.
        retrynum = 5
        i = 1
        while i <= retrynum && !leaseState
          puts "Retrying at iteration #{i}"
          sleep 1
          leaseInfo, leaseState, leaseError = nfcLease.collect 'info', 'state', 'error'
          i += 1
        end
        if leaseState != "ready"
          raise "NFC lease is no longer ready: #{leaseState}: #{leaseError}"
        end
        if leaseInfo == nil
          raise "NFC lease disappeared?"
        end
        deviceUrl = leaseInfo.deviceUrl.find{|x| x.importKey == fileItem.deviceId}
        if !deviceUrl
          raise "Couldn't find deviceURL for device '#{fileItem.deviceId}'"
        end

        ovfFilename = opts[:uri].to_s
        tmp = ovfFilename.split(/\//)
        tmp.pop
        tmp << fileItem.path
        filename = tmp.join("/")

        # If filename doesn't have a URI scheme, we're considering it a local file
        if URI(filename).scheme.nil?
          filename = "file://" + filename
        end

        method = fileItem.create ? "PUT" : "POST"

        keepAliveThread = Thread.new do
          while true
            nfcLease.HttpNfcLeaseProgress(:percent => progress.to_i)
            sleep 1 * 60
          end
        end

        i = 1
        ip = nil
        begin
          begin
            puts "Iteration #{i}: Trying to get host's IP address ..."
            ip = opts[:host].config.network.vnic[0].spec.ip.ipAddress
          rescue Exception=>e
            puts "Iteration #{i}: Couldn't get host's IP address: #{e}"
          end
          sleep 1
          i += 1
        end while i <= 5 && !ip
        raise "Couldn't get host's IP address" unless ip
        href = deviceUrl.url.gsub("*", ip)
        downloadCmd = "#{CURLBIN} -L '#{URI::escape(filename)}'"
        uploadCmd = "#{CURLBIN} -Ss -X #{method} --insecure -T - -H 'Content-Type: application/x-vnd.vmware-streamVmdk' '#{URI::escape(href)}'"
        # Previously we used to append "-H 'Content-Length: #{fileItem.size}'"
        # to the uploadCmd. It is not clear to me why, but that leads to 
        # trucation of the uploaded disk. Without this option curl can't tell
        # the progress, but who cares
        system("#{downloadCmd} | #{uploadCmd}", STDOUT => "/dev/null")
        
        keepAliveThread.kill
        keepAliveThread.join
        
        progress += (90.0 / result.fileItem.length)
        nfcLease.HttpNfcLeaseProgress(:percent => progress.to_i)
      end

      nfcLease.HttpNfcLeaseProgress(:percent => 100)
      raise nfcLease.error if nfcLease.state == "error"
      i = 1
      vm = nil
      begin
        begin
          puts "Iteration #{i}: Trying to access nfcLease.info.entity ..."
          vm = nfcLease.info.entity
        rescue Exception=>e
          puts "Iteration #{i}: Couldn't access nfcLease.info.entity: #{e}"
        end
        sleep 1
        i += 1
      end while i <= 5 && !vm
      raise "Couldn't access nfcLease.info.entity" unless vm

      # Ignore sporadic connection errors caused by PR 1019166..
      # Three attempts are made to execute HttpNfcLeaseComplete.
      # Not critical if none goes through, as long as vm is obtained
      #
      # TODO: find the reason why HttpNfcLeaseComplete gets a wrong
      # response (RetrievePropertiesResponse)
      i = 0
      begin
        nfcLease.HttpNfcLeaseComplete
        puts "HttpNfcLeaseComplete succeeded"
      rescue RbVmomi::VIM::InvalidState
        puts "HttpNfcLeaseComplete already finished.."
      rescue Exception => e
        puts "HttpNfcLeaseComplete failed at iteration #{i} with exception: #{e}"
        i += 1
        retry if i < 3
        puts "Giving up HttpNfcLeaseComplete.."
      end
      vm
    end
  rescue Exception
    (nfcLease.HttpNfcLeaseAbort rescue nil) if nfcLease
    raise
  end
  
  def _handle_ost ost, opts = {}
    ost = Nokogiri::XML(ost)
    if opts[:vservice] == ['com.vmware.vim.vsm:extension_vservice']
      ost.xpath('//vmw:Annotations/vmw:Providers/vmw:Provider').each do |x|
        x['vmw:selected'] = 'selected'
      end
      ost.xpath('//vmw:Annotations/vmw:Providers').each do |x|
        x['vmw:selected'] = 'com.vmware.vim.vsm:extension_vservice'
      end
    end
    ost.to_s
  end
end