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
|