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]