Class: RbVmomi::VIM::OvfManager

Inherits:
Object
  • Object
show all
Defined in:
lib/rbvmomi/vim/OvfManager.rb

Overview

TODO:

Use an HTTP library instead of executing curl.

Note:

deployOVF and requires curl. If curl is not in your PATH then set the CURL environment variable to point to it.

Constant Summary collapse

CURLBIN =
ENV['CURL'] || "curl"

Instance Method Summary collapse

Instance Method Details

#_handle_ost(ost, opts = {}) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rbvmomi/vim/OvfManager.rb', line 192

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

#deployOVF(opts) ⇒ Object

Deploy an OVF.

Parameters:

  • opts (Hash)

    The options hash.

Options Hash (opts):

  • :uri (String)

    Location of the OVF.

  • :vmName (String)

    Name of the new VM.

  • :vmFolder (VIM::Folder)

    Folder to place the VM in.

  • :host (VIM::HostSystem)

    Host to use.

  • :resourcePool (VIM::ResourcePool)

    Resource pool to use.

  • :datastore (VIM::Datastore)

    Datastore to use.

  • :diskProvisioning (String) — default: thin

    Disk provisioning mode.

  • :networkMappings (Hash)

    Network mappings.

  • :propertyMappings (Hash)

    Property mappings.

  • :deploymentOption (String)

    Deployment option key.



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
# File 'lib/rbvmomi/vim/OvfManager.rb', line 23

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 => opts[: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}", :out => "/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