From 729906f5fb1e7f6c62c43f39a04d8b68b24c7568 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 26 Aug 2026 11:32:44 +0200 Subject: [PATCH 1/2] vmware-base: fix template copy from secondary to primary --- .../hypervisor/vmware/util/VmwareContext.java | 132 +++++++++++++----- 1 file changed, 99 insertions(+), 33 deletions(-) diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java index c2b34aea5d93..7e1d54b9a416 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; @@ -387,25 +388,35 @@ public void uploadFile(String httpMethod, String urlString, String localFileName OutputStream out = null; InputStream in = null; BufferedReader br = null; + long bytesWritten = 0; try { - out = conn.getOutputStream(); - in = new FileInputStream(localFileName); - byte[] buf = new byte[ChunkSize]; - int len = 0; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); + try { + out = conn.getOutputStream(); + in = new FileInputStream(localFileName); + byte[] buf = new byte[ChunkSize]; + int len = 0; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + bytesWritten += len; + } + out.flush(); + } catch (IOException e) { + throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes: %s", + localFileName, httpMethod, urlString, bytesWritten, new File(localFileName).length(), e.getMessage()), e); + } finally { + if (in != null) + in.close(); + + if (out != null) + out.close(); + + if (br != null) + br.close(); } - out.flush(); - } finally { - if (in != null) - in.close(); - - if (out != null) - out.close(); - if (br != null) - br.close(); + checkUploadResponse(conn, httpMethod, urlString, localFileName); + } finally { conn.disconnect(); } } @@ -444,33 +455,88 @@ public void uploadVmdkFile(String httpMethod, String urlString, String localFile BufferedOutputStream bos = null; BufferedInputStream is = null; + long bytesWrittenThisCall = 0; try { - bos = new BufferedOutputStream(conn.getOutputStream()); - is = new BufferedInputStream(new FileInputStream(localFileName)); - int bufferSize = ChunkSize; - byte[] buffer = new byte[bufferSize]; - while (true) { - int bytesRead = is.read(buffer, 0, bufferSize); - if (bytesRead == -1) { - break; + try { + bos = new BufferedOutputStream(conn.getOutputStream()); + is = new BufferedInputStream(new FileInputStream(localFileName)); + int bufferSize = ChunkSize; + byte[] buffer = new byte[bufferSize]; + while (true) { + int bytesRead = is.read(buffer, 0, bufferSize); + if (bytesRead == -1) { + break; + } + bos.write(buffer, 0, bytesRead); + totalBytesUpdated += bytesRead; + bytesWrittenThisCall += bytesRead; + bos.flush(); + if (progressUpdater != null) + progressUpdater.action(new Long(totalBytesUpdated)); } - bos.write(buffer, 0, bytesRead); - totalBytesUpdated += bytesRead; bos.flush(); - if (progressUpdater != null) - progressUpdater.action(new Long(totalBytesUpdated)); + } catch (IOException e) { + throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes for this file " + + "(%d bytes total written so far for this import): %s", + localFileName, httpMethod, urlString, bytesWrittenThisCall, new File(localFileName).length(), totalBytesUpdated, e.getMessage()), e); + } finally { + if (is != null) + is.close(); + if (bos != null) + bos.close(); } - bos.flush(); - } finally { - if (is != null) - is.close(); - if (bos != null) - bos.close(); + checkUploadResponse(conn, httpMethod, urlString, localFileName); + } finally { conn.disconnect(); } } + /** + * HttpURLConnection does not surface a failed request just because the client finished writing the + * request body without an IOException: with chunked transfer encoding many HTTP servers, including + * ESXi's NFC endpoint, read and discard the whole body before responding with an error status (e.g. a + * VMFS file lock, an out-of-space datastore, or an authentication/session failure). Silently ignoring + * the response code is exactly how a rejected/short write can look like a "successful" upload to + * CloudStack. Surface the actual status code, reason phrase and (if any) response body so failures like + * "file locked" are visible in the CloudStack logs instead of only in vCenter/ESXi's own logs, if at all. + */ + private void checkUploadResponse(HttpURLConnection conn, String httpMethod, String urlString, String localFileName) throws IOException { + int responseCode; + try { + responseCode = conn.getResponseCode(); + } catch (IOException e) { + throw new IOException(String.format("Unable to read the response for %s %s (uploading %s): %s", + httpMethod, urlString, localFileName, e.getMessage()), e); + } + + if (responseCode < 200 || responseCode >= 300) { + String responseBody = readResponseBodyQuietly(conn); + boolean hasBody = responseBody != null && !responseBody.trim().isEmpty(); + throw new IOException(String.format( + "%s %s rejected the upload of %s with HTTP %d %s%s", + httpMethod, urlString, localFileName, responseCode, conn.getResponseMessage(), + hasBody ? (": " + responseBody) : "")); + } + } + + private String readResponseBodyQuietly(HttpURLConnection conn) { + InputStream errorStream = conn.getErrorStream(); + if (errorStream == null) { + return null; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream, getCharSetFromConnection(conn)))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null && sb.length() < 1024) { + sb.append(line); + } + return sb.toString(); + } catch (IOException e) { + return null; + } + } + public long downloadVmdkFile(String urlString, String localFileName, AtomicLong totalBytesDownloaded, ActionDelegate progressUpdater) throws Exception { HttpURLConnection conn = getRawHTTPConnection(urlString); From 6e75c5f318416b7649feef96a0b51aa7574e2777 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 1 Sep 2026 11:39:30 +0200 Subject: [PATCH 2/2] vmware-base: refactoring --- .../hypervisor/vmware/util/VmwareContext.java | 105 ++++++++---------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java index 7e1d54b9a416..3b7c0627fe33 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareContext.java @@ -385,42 +385,32 @@ public void uploadFile(String httpMethod, String urlString, String localFileName conn.setRequestProperty("content-type", contentType); conn.setRequestProperty("content-length", Long.toString(new File(localFileName).length())); connectWithRetry(conn); - OutputStream out = null; - InputStream in = null; - BufferedReader br = null; - long bytesWritten = 0; try { - try { - out = conn.getOutputStream(); - in = new FileInputStream(localFileName); - byte[] buf = new byte[ChunkSize]; - int len = 0; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - bytesWritten += len; - } - out.flush(); - } catch (IOException e) { - throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes: %s", - localFileName, httpMethod, urlString, bytesWritten, new File(localFileName).length(), e.getMessage()), e); - } finally { - if (in != null) - in.close(); - - if (out != null) - out.close(); - - if (br != null) - br.close(); - } - + writeFileToConnection(conn, httpMethod, urlString, localFileName); checkUploadResponse(conn, httpMethod, urlString, localFileName); } finally { conn.disconnect(); } } + private void writeFileToConnection(HttpURLConnection conn, String httpMethod, String urlString, String localFileName) throws IOException { + long bytesWritten = 0; + try (OutputStream out = conn.getOutputStream(); + InputStream in = new FileInputStream(localFileName)) { + byte[] buf = new byte[ChunkSize]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + bytesWritten += len; + } + out.flush(); + } catch (IOException e) { + throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes: %s", + localFileName, httpMethod, urlString, bytesWritten, new File(localFileName).length(), e.getMessage()), e); + } + } + private Charset getCharSetFromConnection(HttpURLConnection conn) { String charsetName = conn.getContentEncoding(); Charset charset; @@ -453,45 +443,40 @@ public void uploadVmdkFile(String httpMethod, String urlString, String localFile conn.setRequestProperty("content-length", Long.toString(new File(localFileName).length())); connectWithRetry(conn); - BufferedOutputStream bos = null; - BufferedInputStream is = null; - long bytesWrittenThisCall = 0; try { - try { - bos = new BufferedOutputStream(conn.getOutputStream()); - is = new BufferedInputStream(new FileInputStream(localFileName)); - int bufferSize = ChunkSize; - byte[] buffer = new byte[bufferSize]; - while (true) { - int bytesRead = is.read(buffer, 0, bufferSize); - if (bytesRead == -1) { - break; - } - bos.write(buffer, 0, bytesRead); - totalBytesUpdated += bytesRead; - bytesWrittenThisCall += bytesRead; - bos.flush(); - if (progressUpdater != null) - progressUpdater.action(new Long(totalBytesUpdated)); - } - bos.flush(); - } catch (IOException e) { - throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes for this file " + - "(%d bytes total written so far for this import): %s", - localFileName, httpMethod, urlString, bytesWrittenThisCall, new File(localFileName).length(), totalBytesUpdated, e.getMessage()), e); - } finally { - if (is != null) - is.close(); - if (bos != null) - bos.close(); - } - + writeVmdkFileToConnection(conn, httpMethod, urlString, localFileName, totalBytesUpdated, progressUpdater); checkUploadResponse(conn, httpMethod, urlString, localFileName); } finally { conn.disconnect(); } } + private void writeVmdkFileToConnection(HttpURLConnection conn, String httpMethod, String urlString, String localFileName, long totalBytesUpdated, + ActionDelegate progressUpdater) throws IOException { + long bytesWrittenThisCall = 0; + try (BufferedOutputStream bos = new BufferedOutputStream(conn.getOutputStream()); + BufferedInputStream is = new BufferedInputStream(new FileInputStream(localFileName))) { + byte[] buffer = new byte[ChunkSize]; + while (true) { + int bytesRead = is.read(buffer, 0, ChunkSize); + if (bytesRead == -1) { + break; + } + bos.write(buffer, 0, bytesRead); + totalBytesUpdated += bytesRead; + bytesWrittenThisCall += bytesRead; + bos.flush(); + if (progressUpdater != null) + progressUpdater.action(new Long(totalBytesUpdated)); + } + bos.flush(); + } catch (IOException e) { + throw new IOException(String.format("Upload of %s to %s %s failed after writing %d of %d bytes for this file " + + "(%d bytes total written so far for this import): %s", + localFileName, httpMethod, urlString, bytesWrittenThisCall, new File(localFileName).length(), totalBytesUpdated, e.getMessage()), e); + } + } + /** * HttpURLConnection does not surface a failed request just because the client finished writing the * request body without an IOException: with chunked transfer encoding many HTTP servers, including