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.

Instance Method Summary collapse

Instance Method Details

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


185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rbvmomi/vim/OvfManager.rb', line 185

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.


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

def deployOVF opts
  opts = { networkMappings: {},
           propertyMappings: {},
           diskProvisioning: :thin }.merge opts

  %w(uri vmName vmFolder host resourcePool datastore).each do |k|
    raise "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?

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

  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}"
    puts 'WARNING: OVF upload NFC lease timeout less than 4 minutes' if timeout < 4 * 60
    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
      raise "NFC lease is no longer ready: #{leaseState}: #{leaseError}" if leaseState != 'ready'
      raise 'NFC lease disappeared?' if leaseInfo == nil

      deviceUrl = leaseInfo.deviceUrl.find{ |x| x.importKey == fileItem.deviceId }
      raise "Couldn't find deviceURL for device '#{fileItem.deviceId}'" if !deviceUrl

      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
      filename = 'file://' + filename if URI(filename).scheme.nil?

      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