Copilot commented on code in PR #13294:
URL: https://github.com/apache/cloudstack/pull/13294#discussion_r3413194152


##########
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java:
##########
@@ -399,6 +423,249 @@ protected boolean 
performInstanceConversionUsingVddk(RemoteInstanceTO vmwareInst
         }
     }
 
+    protected boolean 
performInstanceConversionUsingVddkDirectToRbd(ConvertInstanceCommand cmd,
+                                                                    
KVMStoragePool targetPool,
+                                                                    String 
originalVMName,
+                                                                    String 
vddkLibDir,
+                                                                    String 
libguestfsBackend,
+                                                                    String 
vddkTransports,
+                                                                    String 
configuredVddkThumbprint,
+                                                                    long 
timeout,
+                                                                    boolean 
verboseModeEnabled,
+                                                                    String 
temporaryConvertUuid,
+                                                                    
LibvirtComputingResource serverResource) {
+        RemoteInstanceTO vmwareInstance = cmd.getSourceInstance();
+        List<VmwareVddkSourceDiskTO> sourceDisks = 
cmd.getVmwareVddkSourceDisks();
+        if (sourceDisks == null || sourceDisks.isEmpty()) {
+            logger.error("({}) Direct RBD VDDK import requires VMware source 
disk metadata", originalVMName);
+            return false;
+        }
+
+        probeRbdQemuAccess(targetPool, temporaryConvertUuid);
+
+        String vcenterPassword = vmwareInstance.getVcenterPassword();
+        if (StringUtils.isBlank(vcenterPassword)) {
+            logger.error("({}) Could not determine vCenter password for {}", 
originalVMName, vmwareInstance.getVcenterHost());
+            return false;
+        }
+
+        String vddkThumbprint = 
StringUtils.trimToNull(configuredVddkThumbprint);
+        if (StringUtils.isBlank(vddkThumbprint)) {
+            vddkThumbprint = 
getVcenterThumbprint(vmwareInstance.getVcenterHost(), timeout, originalVMName);
+        }
+        if (StringUtils.isBlank(vddkThumbprint)) {
+            logger.error("({}) Could not determine vCenter thumbprint for {}", 
originalVMName, vmwareInstance.getVcenterHost());
+            return false;
+        }
+
+        String passwordFilePath = 
String.format("/tmp/v2v.rbd.pass.cloud.%s.%s",
+                StringUtils.defaultIfBlank(vmwareInstance.getVcenterHost(), 
"unknown"),
+                UUID.randomUUID());
+        List<String> createdImages = new ArrayList<>();
+        try {
+            Files.writeString(Path.of(passwordFilePath), vcenterPassword);
+            Files.setPosixFilePermissions(Path.of(passwordFilePath), 
Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
+
+            for (int i = 0; i < sourceDisks.size(); i++) {
+                VmwareVddkSourceDiskTO sourceDisk = sourceDisks.get(i);
+                String imageName = buildRbdImageName(temporaryConvertUuid, i);
+                createdImages.add(imageName);
+                copyVddkSourceDiskToRbd(vmwareInstance, sourceDisk, 
targetPool, imageName, passwordFilePath,
+                        vddkLibDir, vddkTransports, vddkThumbprint, timeout, 
originalVMName);
+            }
+
+            Path inputXml = Files.createTempFile("cloudstack-vddk-rbd-" + 
temporaryConvertUuid, ".xml");
+            Path outputXml = Files.createTempFile("cloudstack-vddk-rbd-" + 
temporaryConvertUuid + "-out", ".xml");
+            Files.writeString(inputXml, 
buildDirectRbdLibvirtXml(temporaryConvertUuid, targetPool, createdImages));
+
+            boolean finalized = runInPlaceFinalization(inputXml, outputXml, 
libguestfsBackend, timeout, verboseModeEnabled, originalVMName, serverResource);
+            if (!finalized) {
+                cleanupRbdImages(targetPool, createdImages, originalVMName);
+            }
+            Files.deleteIfExists(inputXml);
+            Files.deleteIfExists(outputXml);
+            return finalized;
+        } catch (Exception e) {
+            logger.error("({}) Direct RBD VDDK import failed: {}", 
originalVMName, e.getMessage(), e);
+            cleanupRbdImages(targetPool, createdImages, originalVMName);
+            return false;
+        } finally {
+            try {
+                Files.deleteIfExists(Path.of(passwordFilePath));
+            } catch (Exception e) {
+                logger.warn("({}) Failed to delete password file {}: {}", 
originalVMName, passwordFilePath, e.getMessage());
+            }
+        }
+    }
+
+    private void copyVddkSourceDiskToRbd(RemoteInstanceTO vmwareInstance, 
VmwareVddkSourceDiskTO sourceDisk,
+                                         KVMStoragePool targetPool, String 
imageName, String passwordFilePath,
+                                         String vddkLibDir, String 
vddkTransports, String vddkThumbprint,
+                                         long timeout, String originalVMName) {
+        if (StringUtils.isBlank(sourceDisk.getSourceDiskPath())) {
+            throw new CloudRuntimeException(String.format("VMware source disk 
%s does not have a VMDK path", sourceDisk.getDiskId()));
+        }
+        String rbdImagePath = targetPool.getSourceDir() + "/" + imageName;
+        String qemuRbdTarget = KVMPhysicalDisk.RBDStringBuilder(targetPool, 
rbdImagePath);
+        StringBuilder command = new StringBuilder();
+        command.append("nbdkit -r -U - vddk ");
+        
command.append("file=").append(shellQuote(sourceDisk.getSourceDiskPath())).append("
 ");
+        
command.append("server=").append(shellQuote(vmwareInstance.getVcenterHost())).append("
 ");
+        
command.append("user=").append(shellQuote(vmwareInstance.getVcenterUsername())).append("
 ");
+        
command.append("password=+").append(shellQuote(passwordFilePath)).append(" ");
+        if (StringUtils.isNotBlank(vmwareInstance.getVmwareMoref())) {
+            command.append("vm=").append(shellQuote("moref=" + 
vmwareInstance.getVmwareMoref())).append(" ");
+        } else {
+            
command.append("vm=").append(shellQuote(vmwareInstance.getInstanceName())).append("
 ");
+        }
+        command.append("libdir=").append(shellQuote(vddkLibDir)).append(" ");
+        
command.append("thumbprint=").append(shellQuote(vddkThumbprint)).append(" ");
+        if (StringUtils.isNotBlank(vddkTransports)) {
+            
command.append("transports=").append(shellQuote(vddkTransports)).append(" ");
+        }
+        String runCommand = "qemu-img convert -f raw -O raw \"$uri\" " + 
shellQuote(qemuRbdTarget);
+        command.append("--run ").append(shellQuote(runCommand));
+
+        Script script = new Script("/bin/bash", timeout, logger);
+        script.add("-c");
+        script.add(command.toString());
+        String logPrefix = String.format("(%s) vddk to rbd disk %s", 
originalVMName, sourceDisk.getDiskId());
+        OutputInterpreter.LineByLineOutputLogger outputLogger = new 
OutputInterpreter.LineByLineOutputLogger(logger, logPrefix);
+        logger.info("({}) Copying VMware disk {} to RBD image {}", 
originalVMName, sourceDisk.getSourceDiskPath(), rbdImagePath);
+        script.execute(outputLogger);
+        if (script.getExitValue() != 0) {
+            throw new CloudRuntimeException(String.format("Failed to copy 
VMware disk %s to RBD image %s", sourceDisk.getSourceDiskPath(), rbdImagePath));
+        }
+    }
+
+    private boolean runInPlaceFinalization(Path inputXml, Path outputXml, 
String libguestfsBackend, long timeout,
+                                           boolean verboseModeEnabled, String 
originalVMName,
+                                           LibvirtComputingResource 
serverResource) {
+        StringBuilder command = new StringBuilder();
+        command.append("export 
LIBGUESTFS_BACKEND=").append(shellQuote(libguestfsBackend)).append(" && ");
+        if (serverResource.hostSupportsVirtV2vInPlaceBinary()) {
+            command.append("virt-v2v-in-place --root first -i libvirtxml ")
+                    .append(shellQuote(inputXml.toString())).append(" -O ")
+                    .append(shellQuote(outputXml.toString())).append(" ");
+        } else if (serverResource.hostSupportsVirtV2vInPlaceOption()) {
+            command.append("virt-v2v --root first -i libvirtxml ")
+                    .append(shellQuote(inputXml.toString())).append(" 
--in-place ");
+        } else {
+            logger.error("({}) No virt-v2v in-place finalization method is 
available", originalVMName);
+            return false;
+        }
+        if (verboseModeEnabled) {
+            command.append("-v ");
+        }
+
+        Script script = new Script("/bin/bash", timeout, logger);
+        script.add("-c");
+        script.add(command.toString());
+        OutputInterpreter.LineByLineOutputLogger outputLogger = new 
OutputInterpreter.LineByLineOutputLogger(logger,
+                String.format("(%s) virt-v2v rbd in-place", originalVMName));
+        script.execute(outputLogger);
+        return script.getExitValue() == 0;
+    }
+
+    private String buildDirectRbdLibvirtXml(String temporaryConvertUuid, 
KVMStoragePool targetPool, List<String> imageNames) {
+        StringBuilder xml = new StringBuilder();
+        xml.append("<domain type='kvm'>\n");
+        xml.append("  <name>").append(xmlEscape("cloudstack-vddk-rbd-" + 
temporaryConvertUuid)).append("</name>\n");
+        xml.append("  <memory unit='KiB'>1048576</memory>\n");
+        xml.append("  <vcpu>1</vcpu>\n");
+        xml.append("  <os><type>hvm</type><boot dev='hd'/></os>\n");
+        xml.append("  <devices>\n");
+        for (int i = 0; i < imageNames.size(); i++) {
+            String imageName = imageNames.get(i);
+            xml.append("    <disk type='network' device='disk'>\n");
+            xml.append("      <driver name='qemu' type='raw'/>\n");
+            xml.append("      <source protocol='rbd' 
name='").append(xmlEscape(targetPool.getSourceDir() + "/" + 
imageName)).append("'>\n");
+            for (String sourceHost : 
StringUtils.split(StringUtils.defaultString(targetPool.getSourceHost()), ",")) {
+                xml.append("        <host 
name='").append(xmlEscape(sourceHost.replace("[", "").replace("]", 
""))).append("'");
+                if (targetPool.getSourcePort() != 0) {
+                    xml.append(" 
port='").append(targetPool.getSourcePort()).append("'");
+                }
+                xml.append("/>\n");
+            }
+            xml.append("      </source>\n");
+            if (StringUtils.isNotBlank(targetPool.getAuthUserName())) {
+                xml.append("      <auth 
username='").append(xmlEscape(targetPool.getAuthUserName())).append("'>\n");
+                xml.append("        <secret type='ceph' 
uuid='").append(xmlEscape(targetPool.getUuid())).append("'/>\n");
+                xml.append("      </auth>\n");
+            }
+            xml.append("      <target 
dev='").append(diskTargetName(i)).append("' bus='scsi'/>\n");
+            xml.append("    </disk>\n");
+        }
+        xml.append("  </devices>\n");
+        xml.append("</domain>\n");
+        return xml.toString();
+    }
+
+    private String buildRbdImageName(String temporaryConvertUuid, int 
position) {
+        return String.format("%s-disk-%03d", temporaryConvertUuid, position);
+    }
+
+    private String diskTargetName(int index) {
+        StringBuilder target = new StringBuilder("sd");
+        int value = index;
+        do {
+            target.insert(2, (char) ('a' + (value % 26)));
+            value = value / 26 - 1;
+        } while (value >= 0);
+        return target.toString();
+    }
+
+    private void probeRbdQemuAccess(KVMStoragePool pool, String 
temporaryConvertUuid) {
+        String probeName = temporaryConvertUuid + "-probe-" + 
UUID.randomUUID();

Review Comment:
   The RBD probe image name is prefixed with `temporaryConvertUuid`, which is 
later used as the prefix to discover converted disks on the destination pool. 
If probe cleanup fails, the leftover `*-probe-*` image will be misidentified as 
a converted disk (wrong disk count/order, potential import failure). Use a 
probe name that does not start with `temporaryConvertUuid`.



##########
PendingReleaseNotes:
##########
@@ -39,3 +39,11 @@ example.ver.1 > example.ver.2:
     which can now be attached to Instances. This is to prevent the Secondary
     Storage to grow to enormous sizes as Linux Distributions keep growing in
     size while a stripped down Linux should fit on a 2.88MB floppy.
+
+4.23.0.0:

Review Comment:
   This branch's Maven version is 4.22.2.0-SNAPSHOT (pom.xml), but the release 
note entry is filed under 4.23.0.0. This will miscategorize the change in the 
4.22 release notes; the header should match the branch version.



##########
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapper.java:
##########
@@ -185,16 +206,12 @@ protected List<KVMPhysicalDisk> 
moveTemporaryDisksToDestination(List<KVMPhysical
         }
         for (int i = 0; i < temporaryDisks.size(); i++) {
             String poolPath = destinationStoragePools.get(i);
-            KVMStoragePool destinationPool = 
storagePoolMgr.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, 
poolPath);
+            Storage.StoragePoolType poolType = 
getDestinationStoragePoolType(destinationStoragePoolTypes, i);
+            KVMStoragePool destinationPool = 
storagePoolMgr.getStoragePool(poolType, poolPath);
             if (destinationPool == null) {
                 String err = String.format("Could not find a storage pool by 
URI: %s", poolPath);

Review Comment:
   The error message says "storage pool by URI", but the value passed here is 
the storage pool UUID (e.g. from `StoragePoolVO.getUuid()`). This wording is 
misleading when diagnosing failures.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to