Added vddk support in vmware to kvm migrations#12970
Added vddk support in vmware to kvm migrations#12970harikrishna-patnala wants to merge 14 commits intoapache:4.22from
Conversation
b95cfb7 to
7dc5d50
Compare
|
@blueorangutan package |
|
@harikrishna-patnala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 4.22 #12970 +/- ##
==========================================
Coverage 17.60% 17.61%
- Complexity 15677 15699 +22
==========================================
Files 5918 5918
Lines 531681 532140 +459
Branches 65005 65084 +79
==========================================
+ Hits 93623 93749 +126
- Misses 427498 427810 +312
- Partials 10560 10581 +21
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@blueorangutan package |
|
@harikrishna-patnala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress. |
|
@blueorangutan package |
|
@harikrishna-patnala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress. |
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17380 |
|
@blueorangutan package |
| # Optional vCenter SHA1 thumbprint for VMware to KVM conversion via VDDK, passed as | ||
| # -io vddk-thumbprint=<value>. If unset, CloudStack computes it on the KVM host via openssl. | ||
| #vddk.thumbprint= | ||
|
|
There was a problem hiding this comment.
@harikrishna-patnala can you update the docs with these settings here: https://docs.cloudstack.apache.org/en/4.22.0.0/adminguide/virtual_machines.html#importing-virtual-machines-from-vmware-into-kvm
There was a problem hiding this comment.
added a doc PR here apache/cloudstack-documentation#640
| # Instance conversion VIRT_V2V_TMPDIR env var | ||
| #convert.instance.env.virtv2v.tmpdir= | ||
|
|
||
| # LIBGUESTFS backend to use for VMware to KVM conversion via VDDK (default: direct) |
There was a problem hiding this comment.
should keep other possible options in the comment (other than direct) ?
api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java
Outdated
Show resolved
Hide resolved
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Outdated
Show resolved
Hide resolved
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Outdated
Show resolved
Hide resolved
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Outdated
Show resolved
Hide resolved
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Outdated
Show resolved
Hide resolved
|
CheckConvertInstanceCommand should ensure vddk support on host when usevddk is enabled, in addition to virt-v2v, and skip checking ovf export (in LibvirtCheckConvertInstanceCommandWrapper) |
There was a problem hiding this comment.
Pull request overview
This PR adds a VDDK-based VMware→KVM conversion path (via virt-v2v) to speed up importVm migrations, with host-level defaults (agent.properties) and API-level overrides (importVm details), plus UI support and tests.
Changes:
- Introduces
usevddkAPI parameter and propagates VDDK settings through the VMware→KVM import pipeline intoConvertInstanceCommand. - Implements VDDK conversion execution on KVM hosts (password-file handling, vCenter thumbprint retrieval, virt-v2v option detection) and advertises host VDDK capability.
- Updates UI to toggle VDDK mode and adjusts conversion options; adds i18n string and expands unit test coverage.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/views/tools/ImportUnmanagedInstance.vue | Adds “Use VDDK” toggle and alters conversion option interactions/params. |
| ui/public/locales/en.json | Adds UI label for VDDK toggle. |
| server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | Wires usevddk and details overrides into conversion flow; adds validation. |
| server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java | Adds test cases for VDDK behavior and validations. |
| core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java | Adds VDDK-related fields to conversion command. |
| api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java | Adds usevddk request parameter. |
| api/src/main/java/org/apache/cloudstack/api/ApiConstants.java | Adds USE_VDDK constant. |
| api/src/main/java/com/cloud/host/Host.java | Adds host detail key for VDDK support. |
| api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.java | Adds cluster/host fields needed for VPX URL construction. |
| engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java | Persists reported host VDDK support detail. |
| plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java | Loads VDDK agent.properties, detects virt-v2v password option, reports VDDK support. |
| plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java | Reports host.vddk.support in Ready response. |
| plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java | Adds VDDK conversion execution path and thumbprint parsing. |
| plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java | Adds unit tests around VDDK conversion behavior. |
| agent/src/main/java/com/cloud/agent/properties/AgentProperties.java | Defines new agent properties for VDDK settings. |
| agent/conf/agent.properties | Documents new VDDK-related configuration keys. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| String vcenterPassword = vmwareInstance.getVcenterPassword(); | ||
| if (StringUtils.isBlank(vcenterPassword)) { | ||
| logger.error("({}) Could not determine vCenter password for {}", originalVMName, vmwareInstance.getVcenterHost()); | ||
| return false; | ||
| } | ||
|
|
||
| String passwordFilePath = String.format("/root/v2v.pass.cloud.%s", | ||
| StringUtils.defaultIfBlank(vmwareInstance.getVcenterHost(), "unknown")); | ||
| try { | ||
| Files.writeString(Path.of(passwordFilePath), vcenterPassword); | ||
| logger.debug("({}) Written vCenter password to {}", originalVMName, passwordFilePath); | ||
| } catch (Exception e) { | ||
| logger.error("({}) Failed to write vCenter password file {}: {}", originalVMName, passwordFilePath, e.getMessage()); | ||
| return false; | ||
| } | ||
|
|
||
| String vpxUrl = buildVpxUrl(vmwareInstance, originalVMName); | ||
|
|
||
| StringBuilder cmd = new StringBuilder(); | ||
|
|
||
| String effectiveLibguestfsBackend = StringUtils.defaultIfBlank(libguestfsBackend, "direct"); | ||
| cmd.append("export LIBGUESTFS_BACKEND=").append(effectiveLibguestfsBackend).append(" && "); | ||
|
|
||
| cmd.append("virt-v2v "); | ||
| cmd.append("--root first "); | ||
| cmd.append("-ic '").append(vpxUrl).append("' "); | ||
| if (StringUtils.isBlank(passwordOption)) { | ||
| logger.error("({}) Could not determine supported password file option for virt-v2v", originalVMName); | ||
| return false; | ||
| } | ||
|
|
||
| cmd.append(passwordOption).append(" ").append(passwordFilePath).append(" "); | ||
| cmd.append("-it vddk "); | ||
| cmd.append("-io vddk-libdir=").append(vddkLibDir).append(" "); | ||
| 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; | ||
| } |
There was a problem hiding this comment.
performInstanceConversionVddk writes the vCenter password to a fixed path under /root derived from the vCenter host. This is vulnerable to races between concurrent conversions (overwriting/deleting each other’s password file) and may leave the password on disk on early returns (e.g., when passwordOption is blank or thumbprint lookup fails). Use a unique temp file (e.g., Files.createTempFile), set restrictive permissions (0600), and ensure deletion happens in a finally block that runs for all exit paths.
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Outdated
Show resolved
Hide resolved
...ain/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
Show resolved
Hide resolved
server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
Show resolved
Hide resolved
| try { | ||
| ProcessBuilder pb = new ProcessBuilder("virt-v2v", "--help"); | ||
| Process process = pb.start(); | ||
|
|
||
| String output = new String(process.getInputStream().readAllBytes()); | ||
| process.waitFor(); | ||
|
|
||
| if (output.contains("-ip <filename>")) { | ||
| return "-ip"; | ||
| } else if (output.contains("--password-file")) { | ||
| return "--password-file"; | ||
| } else { | ||
| LOGGER.error("virt-v2v does not support -ip or --password-file"); | ||
| return null; | ||
| } | ||
| } catch (Exception e) { | ||
| LOGGER.error("Failed to detect virt-v2v password option: {}", e.getMessage()); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
detectPasswordFileOption() runs virt-v2v --help via ProcessBuilder without a timeout and without consuming stderr; if virt-v2v hangs or produces enough stderr output, agent startup could block indefinitely. Consider using the existing Script helper with a timeout (and redirected stderr), and check/handle the process exit code before trusting output.
| try { | |
| ProcessBuilder pb = new ProcessBuilder("virt-v2v", "--help"); | |
| Process process = pb.start(); | |
| String output = new String(process.getInputStream().readAllBytes()); | |
| process.waitFor(); | |
| if (output.contains("-ip <filename>")) { | |
| return "-ip"; | |
| } else if (output.contains("--password-file")) { | |
| return "--password-file"; | |
| } else { | |
| LOGGER.error("virt-v2v does not support -ip or --password-file"); | |
| return null; | |
| } | |
| } catch (Exception e) { | |
| LOGGER.error("Failed to detect virt-v2v password option: {}", e.getMessage()); | |
| return null; | |
| } | |
| final int timeout = 30; | |
| final Script script = new Script("/bin/bash", timeout, LOGGER); | |
| script.add("-c"); | |
| script.add("virt-v2v --help 2>&1"); | |
| final AllLinesParser parser = new AllLinesParser(); | |
| final String executionResult = script.execute(parser); | |
| if (executionResult != null) { | |
| LOGGER.error("Failed to detect virt-v2v password option: {}", executionResult); | |
| return null; | |
| } | |
| final String output = parser.getLines(); | |
| if (StringUtils.isBlank(output)) { | |
| LOGGER.error("Failed to detect virt-v2v password option: empty output from virt-v2v --help"); | |
| return null; | |
| } | |
| if (output.contains("-ip <filename>")) { | |
| return "-ip"; | |
| } else if (output.contains("--password-file")) { | |
| return "--password-file"; | |
| } else { | |
| LOGGER.error("virt-v2v does not support -ip or --password-file"); | |
| return null; | |
| } |
server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
Show resolved
Hide resolved
server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
Outdated
Show resolved
Hide resolved
|
@blueorangutan package |
|
@harikrishna-patnala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress. |
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17407 |
|
@blueorangutan package |
|
@harikrishna-patnala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress. |
|
Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 17411 |
| String vddkLibDir = resolveVddkSetting(cmd.getVddkLibDir(), serverResource.getVddkLibDir()); | ||
| if (StringUtils.isBlank(vddkLibDir)) { | ||
| String err = String.format("VDDK lib dir is not configured on the host. " + | ||
| "Set '%s' in agent.properties to use VDDK-based conversion.", "vddk.lib.dir"); |
There was a problem hiding this comment.
| "Set '%s' in agent.properties to use VDDK-based conversion.", "vddk.lib.dir"); | |
| "Set '%s' in agent.properties or in details parameter of the import api calll to use VDDK-based conversion.", "vddk.lib.dir"); |
| return false; | ||
| } | ||
|
|
||
| String passwordFilePath = String.format("/root/v2v.pass.cloud.%s.%s", |
There was a problem hiding this comment.
| String passwordFilePath = String.format("/root/v2v.pass.cloud.%s.%s", | |
| String passwordFilePath = String.format("/tmp/v2v.pass.cloud.%s.%s", |
|
|
||
| boolean isOvfExportSupported = false; | ||
| if (!useVddk) { | ||
| CheckConvertInstanceAnswer conversionSupportAnswer = checkConversionSupportOnHost(convertHost, sourceVMName, false); |
There was a problem hiding this comment.
can add conversion support check for vddk as well? if convert host is from the api cmd and it doesn't support vddk, fail early.
auto selection of convert host (when convert host is not passed from the api cmd) should return null and no hosts with vddk support.
| // Uses KVM Host for OVF export to temporary conversion location, through ovftool | ||
| importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance); | ||
| convertedInstance = convertVmwareInstanceToKVMAfterExportingOVFToConvertLocation( | ||
| // Uses KVM Host for direct conversion using VDDK, or for OVF export to temporary conversion location through ovftool importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance); |
There was a problem hiding this comment.
| // Uses KVM Host for direct conversion using VDDK, or for OVF export to temporary conversion location through ovftool importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance); | |
| // Uses KVM Host for direct conversion using VDDK, or for OVF export to temporary conversion location through ovftool | |
| importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance); |
| } | ||
| if (!volumeApiService.doesStoragePoolSupportDiskOffering(selectedStoragePool, rootDiskOffering)) { | ||
| throw new InvalidParameterValueException(String.format("The root disk offering '%s' is not supported by the selected conversion storage pool '%s'. " + | ||
| "When using VDDK, all selected disk offerings must be compatible with the conversion storage pool, as it will become the primary storage for the imported volumes.", |
There was a problem hiding this comment.
conversion storage pool is option parameter, and for ovf export scenario, secondary storage is used as the default conversion storage. ensure conversion storage pool is set in case of vddk (and update the update the importVm API parameter convertinstancepoolid description that it is the primary storage for vddk enabled conversion).
| if (StringUtils.isBlank(selectedHost.getDetail(Host.HOST_VDDK_SUPPORT))) { | ||
| err = String.format( | ||
| "Cannot use VDDK-based conversion on host %s as the '%s' is not there on the host", | ||
| selectedHost, Host.HOST_VDDK_SUPPORT); |
There was a problem hiding this comment.
in some cases, agent is not restarted after updating vddk libs in the host, so this detail can be blank, check it using CheckConvertInstanceCommand (in checkConversionSupportOnHost())
| } else { | ||
| host.getDetails().put(Host.HOST_VDDK_VERSION, vddkVersion); | ||
| } | ||
| updateNeeded = true; |
There was a problem hiding this comment.
do we need all these params in host_details? Persisting Host.HOST_VDDK_VERSION should be enough when vddk lib is properly set, to indicate vddk support.
Description
This PR introduces the VDDK implementation as part of VMware to KVM migration which reduces the migration time by half when compared to the existing implementation with exportOVF
Solution: Introduce VDDK configuration at both the host agent level and per-API-call level, wired through the migration execution pipeline on the KVM host.
Key Capabilities
agent.propertieskeys configure VDDK defaults per KVM hostimportVmdetails, overriding host defaults-ipor--password-filebased on virt-v2v version, and deleted immediately after useopenssl s_clientConfiguration Keys
Precedence
Doc PR: apache/cloudstack-documentation#640
Types of changes
Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale
Bug Severity
Screenshots (if appropriate):
How Has This Been Tested?
How did you try to break this feature and the system with this change?