This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch main-fix-vmware-template
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 66df898efcb24700c40cedb01ada40c0cbe1ef60
Author: Wei Zhou <[email protected]>
AuthorDate: Wed Aug 26 11:32:44 2026 +0200

    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 c2b34aea5d9..7e1d54b9a41 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.ByteArrayOutputStream;
 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 class VmwareContext {
         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 class VmwareContext {
 
         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<Long> progressUpdater) throws 
Exception {
         HttpURLConnection conn = getRawHTTPConnection(urlString);
 

Reply via email to