You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2025/10/10 23:00:34 UTC

(cloudstack) branch main updated: VMware to KVM Migrations improvements (#11594)

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

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new b106d6e190b VMware to KVM Migrations improvements (#11594)
b106d6e190b is described below

commit b106d6e190b098ee1e97264952ed59a277e5c360
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Fri Oct 10 20:00:29 2025 -0300

    VMware to KVM Migrations improvements (#11594)
    
    * Add source VM name on virt-v2v migration log entries
    
    * Improve the feedback by displaying the running importing tasks
    
    * Add source VM name prefix on more conversion logs
    
    * Improve listing and also list completed tasks
    
    * Pass extra parameters to virt-v2v if administrator allows via global setting
    
    * Add Force converting directly to storage pool option
    
    * Refactor based on review comments
    
    * Add properties for env vars for the instance conversion
    
    * Add separate component for Import VM Tasks
    
    * applying copilot suggestions from code review
    
    Co-authored-by: Copilot <17...@users.noreply.github.com>
    
    * Fix importing unmanaged instances due to incorrect internal name
    
    * Add VM prefix on each log operation for conversion
    
    * Log the original VM name instead of the cloned VM in case of cloning
    
    * Allow searching storage pool by UUID after conversion to support SharedMountPoint
    
    * Fix search pools logic
    
    * Improve UI and add checks for force convert to pool parameter
    
    * Support Local storage when forceconverttopool is set to true
    
    * Add config key to for allowed extra params and add validation
    
    * Fix params lists
    
    * Fix compile error
    
    * Remove extra stubbings
    
    * Fix extra params execution
    
    ---------
    
    Co-authored-by: Abhishek Kumar <ab...@gmail.com>
    Co-authored-by: Copilot <17...@users.noreply.github.com>
    Co-authored-by: Suresh Kumar Anaparti <su...@gmail.com>
---
 agent/conf/agent.properties                        |   6 +
 .../cloud/agent/properties/AgentProperties.java    |  14 +
 .../org/apache/cloudstack/api/ApiConstants.java    |   4 +
 .../api/command/admin/vm/ImportVmCmd.java          |  20 ++
 .../api/command/admin/vm/ListImportVMTasksCmd.java | 123 +++++++
 .../api/response/ImportVMTaskResponse.java         | 245 ++++++++++++++
 .../org/apache/cloudstack/vm/ImportVmTask.java     |  28 ++
 .../apache/cloudstack/vm/ImportVmTasksManager.java |  38 +++
 .../cloud/agent/api/ConvertInstanceCommand.java    |  17 +-
 .../agent/api/ImportConvertedInstanceCommand.java  |   9 +-
 .../src/main/java/com/cloud/vm/ImportVMTaskVO.java | 259 +++++++++++++++
 .../java/com/cloud/vm/dao/ImportVMTaskDao.java     |  29 ++
 .../java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java |  65 ++++
 .../spring-engine-schema-core-daos-context.xml     |   1 +
 .../resources/META-INF/db/schema-42100to42200.sql  |  30 ++
 .../kvm/resource/LibvirtComputingResource.java     |  26 ++
 .../LibvirtConvertInstanceCommandWrapper.java      |  73 +++--
 ...bvirtImportConvertedInstanceCommandWrapper.java |  16 +-
 .../LibvirtConvertInstanceCommandWrapperTest.java  |  23 +-
 .../cloudstack/vm/ImportVmTasksManagerImpl.java    | 225 +++++++++++++
 .../cloudstack/vm/UnmanagedVMsManagerImpl.java     | 276 ++++++++++++----
 .../spring-server-compute-context.xml              |   2 +
 .../cloudstack/vm/UnmanagedVMsManagerImplTest.java | 104 +++++-
 tools/apidoc/gen_toc.py                            |   3 +-
 ui/public/locales/en.json                          |   5 +-
 ui/src/views/tools/ImportUnmanagedInstance.vue     |  75 ++++-
 ui/src/views/tools/ImportVmTasks.vue               | 150 +++++++++
 ui/src/views/tools/ManageInstances.vue             | 354 ++++++++++++---------
 .../main/java/com/cloud/utils/script/Script.java   |   1 +
 29 files changed, 1945 insertions(+), 276 deletions(-)

diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties
index 2a083de6fe5..0dc5b8211e0 100644
--- a/agent/conf/agent.properties
+++ b/agent/conf/agent.properties
@@ -451,3 +451,9 @@ iscsi.session.cleanup.enabled=false
 
 # If set to true, creates VMs as full clones of their templates on KVM hypervisor. Creates as linked clones otherwise.
 # create.full.clone=false
+
+# Instance conversion TMPDIR env var
+#convert.instance.env.tmpdir=
+
+# Instance conversion VIRT_V2V_TMPDIR env var
+#convert.instance.env.virtv2v.tmpdir=
diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
index 57dc607b920..1561f0d5cfb 100644
--- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
+++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java
@@ -794,6 +794,20 @@ public class AgentProperties{
      */
     public static final Property<Boolean> VIRTV2V_VERBOSE_ENABLED = new Property<>("virtv2v.verbose.enabled", false);
 
+    /**
+     * Set env TMPDIR var for virt-v2v Instance Conversion from VMware to KVM
+     * Data type: String.<br>
+     * Default value: <code>null</code>
+     */
+    public static final Property<String> CONVERT_ENV_TMPDIR = new Property<>("convert.instance.env.tmpdir", null, String.class);
+
+    /**
+     * Set env VIRT_V2V_TMPDIR var for virt-v2v Instance Conversion from VMware to KVM
+     * Data type: String.<br>
+     * Default value: <code>null</code>
+     */
+    public static final Property<String> CONVERT_ENV_VIRTV2V_TMPDIR = new Property<>("convert.instance.env.virtv2v.tmpdir", null, String.class);
+
     /**
      * BGP controll CIDR
      * Data type: String.<br>
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 475b845af9b..27b17d96b8f 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -225,6 +225,7 @@ public class ApiConstants {
     public static final String EVENT_TYPE = "eventtype";
     public static final String EXPIRES = "expires";
     public static final String EXTRA_CONFIG = "extraconfig";
+    public static final String EXTRA_PARAMS = "extraparams";
     public static final String EXTRA_DHCP_OPTION = "extradhcpoption";
     public static final String EXTRA_DHCP_OPTION_NAME = "extradhcpoptionname";
     public static final String EXTRA_DHCP_OPTION_CODE = "extradhcpoptioncode";
@@ -243,6 +244,8 @@ public class ApiConstants {
     public static final String FIRSTNAME = "firstname";
     public static final String FORCED = "forced";
     public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage";
+    public static final String FORCE_CONVERT_TO_POOL = "forceconverttopool";
+
     public static final String FORCE_DELETE_HOST = "forcedeletehost";
     public static final String FORCE_MS_TO_IMPORT_VM_FILES = "forcemstoimportvmfiles";
     public static final String FORCE_UPDATE_OS_TYPE = "forceupdateostype";
@@ -529,6 +532,7 @@ public class ApiConstants {
     public static final String SHOW_CAPACITIES = "showcapacities";
     public static final String SHOW_REMOVED = "showremoved";
     public static final String SHOW_RESOURCE_ICON = "showicon";
+    public static final String SHOW_COMPLETED = "showcompleted";
     public static final String SHOW_INACTIVE = "showinactive";
     public static final String SHOW_UNIQUE = "showunique";
     public static final String SIGNATURE = "signature";
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java
index db43b53ab9a..f7940460d6c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java
@@ -159,6 +159,18 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
             description = "(only for importing VMs from VMware to KVM) optional - if true, forces MS to export OVF from VMware to temporary storage, else uses KVM Host if ovftool is available, falls back to MS if not.")
     private Boolean forceMsToImportVmFiles;
 
+    @Parameter(name = ApiConstants.EXTRA_PARAMS,
+            type = CommandType.STRING,
+            since = "4.22",
+            description = "(only for importing VMs from VMware to KVM) optional - extra parameters to be passed on the virt-v2v command, if allowed by the administrator")
+    private String extraParams;
+
+    @Parameter(name = ApiConstants.FORCE_CONVERT_TO_POOL,
+            type = CommandType.BOOLEAN,
+            since = "4.22",
+            description = "(only for importing VMs from VMware to KVM) optional - if true, forces virt-v2v conversions to write directly on the provided storage pool (avoid using temporary conversion pool).")
+    private Boolean forceConvertToPool;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -248,6 +260,14 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd {
         return EventTypes.EVENT_VM_IMPORT;
     }
 
+    public String getExtraParams() {
+        return extraParams;
+    }
+
+    public boolean getForceConvertToPool() {
+        return BooleanUtils.toBooleanDefaultIfNull(forceConvertToPool, false);
+    }
+
     @Override
     public String getEventDescription() {
         String vmName = getName();
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java
new file mode 100644
index 00000000000..a0c8cb57ee6
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java
@@ -0,0 +1,123 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.admin.vm;
+
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.NetworkRuleConflictException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.user.Account;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.ImportVMTaskResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.vm.ImportVmTasksManager;
+
+import javax.inject.Inject;
+
+@APICommand(name = "listImportVmTasks",
+        description = "List running import virtual machine tasks from a unmanaged hosts into CloudStack",
+        responseObject = ImportVMTaskResponse.class,
+        responseView = ResponseObject.ResponseView.Full,
+        requestHasSensitiveInfo = false,
+        authorized = {RoleType.Admin},
+        since = "4.22")
+public class ListImportVMTasksCmd extends BaseListCmd {
+
+    @Inject
+    public ImportVmTasksManager importVmTasksManager;
+
+    @Parameter(name = ApiConstants.ZONE_ID,
+            type = CommandType.UUID,
+            entityType = ZoneResponse.class,
+            required = true,
+            description = "the zone ID")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.ACCOUNT_ID,
+            type = CommandType.UUID,
+            entityType = AccountResponse.class,
+            description = "the ID of the Account")
+    private Long accountId;
+
+    @Parameter(name = ApiConstants.VCENTER,
+            type = CommandType.STRING,
+            description = "The name/ip of vCenter. Make sure it is IP address or full qualified domain name for host running vCenter server.")
+    private String vcenter;
+
+    @Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID,
+            type = CommandType.UUID,
+            entityType = HostResponse.class,
+            description = "Conversion host of the importing task")
+    private Long convertHostId;
+
+    @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "Whether to list all import tasks.")
+    private boolean listAll = false;
+
+    @Parameter(name = ApiConstants.SHOW_COMPLETED, type = CommandType.BOOLEAN, description = "Whether to list completed tasks.")
+    private boolean showCompleted = false;
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public Long getAccountId() {
+        return accountId;
+    }
+
+    public String getVcenter() {
+        return vcenter;
+    }
+
+    public Long getConvertHostId() {
+        return convertHostId;
+    }
+
+    public boolean isListAll() {
+        return listAll;
+    }
+
+    public boolean isShowCompleted() {
+        return showCompleted;
+    }
+
+    @Override
+    public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
+        ListResponse<ImportVMTaskResponse> response = importVmTasksManager.listImportVMTasks(this);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Account account = CallContext.current().getCallingAccount();
+        if (account != null) {
+            return account.getId();
+        }
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java
new file mode 100644
index 00000000000..dd49127f1bd
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java
@@ -0,0 +1,245 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package org.apache.cloudstack.api.response;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+import java.util.Date;
+
+public class ImportVMTaskResponse extends BaseResponse {
+
+    @SerializedName(ApiConstants.ID)
+    @Param(description = "the ID of importing task")
+    private String id;
+
+    @SerializedName(ApiConstants.ZONE_ID)
+    @Param(description = "the Zone ID")
+    private String zoneId;
+
+    @SerializedName(ApiConstants.ZONE_NAME)
+    @Param(description = "the Zone name")
+    private String zoneName;
+
+    @SerializedName(ApiConstants.ACCOUNT)
+    @Param(description = "the account name")
+    private String accountName;
+
+    @SerializedName(ApiConstants.ACCOUNT_ID)
+    @Param(description = "the ID of account")
+    private String accountId;
+
+    @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID)
+    @Param(description = "the ID of the imported VM (after task is completed)")
+    private String virtualMachineId;
+
+    @SerializedName(ApiConstants.DISPLAY_NAME)
+    @Param(description = "the display name of the importing VM")
+    private String displayName;
+
+    @SerializedName(ApiConstants.VCENTER)
+    @Param(description = "the vcenter name of the importing VM task")
+    private String vcenter;
+
+    @SerializedName(ApiConstants.DATACENTER_NAME)
+    @Param(description = "the datacenter name of the importing VM task")
+    private String datacenterName;
+
+    @SerializedName("sourcevmname")
+    @Param(description = "the source VM name")
+    private String sourceVMName;
+
+    @SerializedName("step")
+    @Param(description = "the current step on the importing VM task")
+    private String step;
+
+    @SerializedName("stepduration")
+    @Param(description = "the duration of the current step")
+    private String stepDuration;
+
+    @SerializedName(ApiConstants.DURATION)
+    @Param(description = "the total task duration")
+    private String duration;
+
+    @SerializedName(ApiConstants.DESCRIPTION)
+    @Param(description = "the current step description on the importing VM task")
+    private String description;
+
+    @SerializedName(ApiConstants.CONVERT_INSTANCE_HOST_ID)
+    @Param(description = "the ID of the host on which the instance is being converted")
+    private String convertInstanceHostId;
+
+    @SerializedName("convertinstancehostname")
+    @Param(description = "the name of the host on which the instance is being converted")
+    private String convertInstanceHostName;
+
+    @SerializedName(ApiConstants.CREATED)
+    @Param(description = "the create date of the importing task")
+    private Date created;
+
+    @SerializedName(ApiConstants.LAST_UPDATED)
+    @Param(description = "the last updated date of the importing task")
+    private Date lastUpdated;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(String zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public String getZoneName() {
+        return zoneName;
+    }
+
+    public void setZoneName(String zoneName) {
+        this.zoneName = zoneName;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public void setAccountName(String accountName) {
+        this.accountName = accountName;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+
+    public String getVirtualMachineId() {
+        return virtualMachineId;
+    }
+
+    public void setVirtualMachineId(String virtualMachineId) {
+        this.virtualMachineId = virtualMachineId;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getVcenter() {
+        return vcenter;
+    }
+
+    public void setVcenter(String vcenter) {
+        this.vcenter = vcenter;
+    }
+
+    public String getDatacenterName() {
+        return datacenterName;
+    }
+
+    public void setDatacenterName(String datacenterName) {
+        this.datacenterName = datacenterName;
+    }
+
+    public String getSourceVMName() {
+        return sourceVMName;
+    }
+
+    public void setSourceVMName(String sourceVMName) {
+        this.sourceVMName = sourceVMName;
+    }
+
+    public String getStep() {
+        return step;
+    }
+
+    public void setStep(String step) {
+        this.step = step;
+    }
+
+    public String getStepDuration() {
+        return stepDuration;
+    }
+
+    public void setStepDuration(String stepDuration) {
+        this.stepDuration = stepDuration;
+    }
+
+    public String getDuration() {
+        return duration;
+    }
+
+    public void setDuration(String duration) {
+        this.duration = duration;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getConvertInstanceHostId() {
+        return convertInstanceHostId;
+    }
+
+    public void setConvertInstanceHostId(String convertInstanceHostId) {
+        this.convertInstanceHostId = convertInstanceHostId;
+    }
+
+    public String getConvertInstanceHostName() {
+        return convertInstanceHostName;
+    }
+
+    public void setConvertInstanceHostName(String convertInstanceHostName) {
+        this.convertInstanceHostName = convertInstanceHostName;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getLastUpdated() {
+        return lastUpdated;
+    }
+
+    public void setLastUpdated(Date lastUpdated) {
+        this.lastUpdated = lastUpdated;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java
new file mode 100644
index 00000000000..96d7cd346f3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.vm;
+
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+public interface ImportVmTask extends Identity, InternalIdentity {
+    enum Step {
+        Prepare, CloningInstance, ConvertingInstance, Importing, Cleaning, Completed
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java
new file mode 100644
index 00000000000..44eed8c531a
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java
@@ -0,0 +1,38 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.vm;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.host.Host;
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.command.admin.vm.ListImportVMTasksCmd;
+import org.apache.cloudstack.api.response.ImportVMTaskResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+
+public interface ImportVmTasksManager {
+
+    ListResponse<ImportVMTaskResponse> listImportVMTasks(ListImportVMTasksCmd cmd);
+
+    ImportVmTask createImportVMTaskRecord(DataCenter zone, Account owner, long userId, String displayName,
+                                            String vcenter, String datacenterName, String sourceVMName,
+                                            Host convertHost, Host importHost);
+
+    void updateImportVMTaskStep(ImportVmTask importVMTaskVO, DataCenter zone, Account owner, Host convertHost,
+                                Host importHost, Long vmId, ImportVmTask.Step step);
+
+    boolean removeImportVMTask(long taskId);
+}
diff --git a/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java
index f938d0ac55d..24336747ccf 100644
--- a/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java
@@ -23,30 +23,37 @@ import com.cloud.hypervisor.Hypervisor;
 public class ConvertInstanceCommand extends Command {
 
     private RemoteInstanceTO sourceInstance;
+    private String originalVMName;
     private Hypervisor.HypervisorType destinationHypervisorType;
     private DataStoreTO conversionTemporaryLocation;
     private String templateDirOnConversionLocation;
     private boolean checkConversionSupport;
     private boolean exportOvfToConversionLocation;
     private int threadsCountToExportOvf = 0;
+    private String extraParams;
 
     public ConvertInstanceCommand() {
     }
 
     public ConvertInstanceCommand(RemoteInstanceTO sourceInstance, Hypervisor.HypervisorType destinationHypervisorType, DataStoreTO conversionTemporaryLocation,
-                                  String templateDirOnConversionLocation, boolean checkConversionSupport, boolean exportOvfToConversionLocation) {
+                                  String templateDirOnConversionLocation, boolean checkConversionSupport, boolean exportOvfToConversionLocation, String sourceVMName) {
         this.sourceInstance = sourceInstance;
         this.destinationHypervisorType = destinationHypervisorType;
         this.conversionTemporaryLocation = conversionTemporaryLocation;
         this.templateDirOnConversionLocation = templateDirOnConversionLocation;
         this.checkConversionSupport = checkConversionSupport;
         this.exportOvfToConversionLocation = exportOvfToConversionLocation;
+        this.originalVMName = sourceVMName;
     }
 
     public RemoteInstanceTO getSourceInstance() {
         return sourceInstance;
     }
 
+    public String getOriginalVMName() {
+        return originalVMName;
+    }
+
     public Hypervisor.HypervisorType getDestinationHypervisorType() {
         return destinationHypervisorType;
     }
@@ -75,6 +82,14 @@ public class ConvertInstanceCommand extends Command {
         this.threadsCountToExportOvf = threadsCountToExportOvf;
     }
 
+    public String getExtraParams() {
+        return extraParams;
+    }
+
+    public void setExtraParams(String extraParams) {
+        this.extraParams = extraParams;
+    }
+
     @Override
     public boolean executeInSequence() {
         return false;
diff --git a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java
index 9d50e852ced..eadfa6556f8 100644
--- a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java
@@ -27,17 +27,20 @@ public class ImportConvertedInstanceCommand extends Command {
     private List<String> destinationStoragePools;
     private DataStoreTO conversionTemporaryLocation;
     private String temporaryConvertUuid;
+    private boolean forceConvertToPool;
 
     public ImportConvertedInstanceCommand() {
     }
 
     public ImportConvertedInstanceCommand(RemoteInstanceTO sourceInstance,
                                           List<String> destinationStoragePools,
-                                          DataStoreTO conversionTemporaryLocation, String temporaryConvertUuid) {
+                                          DataStoreTO conversionTemporaryLocation, String temporaryConvertUuid,
+                                          boolean forceConvertToPool) {
         this.sourceInstance = sourceInstance;
         this.destinationStoragePools = destinationStoragePools;
         this.conversionTemporaryLocation = conversionTemporaryLocation;
         this.temporaryConvertUuid = temporaryConvertUuid;
+        this.forceConvertToPool = forceConvertToPool;
     }
 
     public RemoteInstanceTO getSourceInstance() {
@@ -56,6 +59,10 @@ public class ImportConvertedInstanceCommand extends Command {
         return temporaryConvertUuid;
     }
 
+    public boolean isForceConvertToPool() {
+        return forceConvertToPool;
+    }
+
     @Override
     public boolean executeInSequence() {
         return false;
diff --git a/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java
new file mode 100644
index 00000000000..e472b56460a
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java
@@ -0,0 +1,259 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package com.cloud.vm;
+
+import org.apache.cloudstack.vm.ImportVmTask;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "import_vm_task")
+public class ImportVMTaskVO implements ImportVmTask {
+
+    public ImportVMTaskVO(long zoneId, long accountId, long userId, String displayName,
+                          String vcenter, String datacenter, String sourceVMName, long convertHostId, long importHostId) {
+        this.zoneId = zoneId;
+        this.accountId = accountId;
+        this.userId = userId;
+        this.displayName = displayName;
+        this.vcenter = vcenter;
+        this.datacenter = datacenter;
+        this.sourceVMName = sourceVMName;
+        this.step = Step.Prepare;
+        this.uuid = UUID.randomUUID().toString();
+        this.convertHostId = convertHostId;
+        this.importHostId = importHostId;
+    }
+
+    public ImportVMTaskVO() {
+    }
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "zone_id")
+    private long zoneId;
+
+    @Column(name = "account_id")
+    private long accountId;
+
+    @Column(name = "user_id")
+    private long userId;
+
+    @Column(name = "vm_id")
+    private Long vmId;
+    @Column(name = "display_name")
+    private String displayName;
+
+    @Column(name = "vcenter")
+    private String vcenter;
+
+    @Column(name = "datacenter")
+    private String datacenter;
+
+    @Column(name = "source_vm_name")
+    private String sourceVMName;
+
+    @Column(name = "convert_host_id")
+    private long convertHostId;
+
+    @Column(name = "import_host_id")
+    private long importHostId;
+
+    @Column(name = "step")
+    private Step step;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "duration")
+    private Long duration;
+
+    @Column(name = "created")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date created;
+
+    @Column(name = "updated")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date updated;
+
+    @Column(name = "removed")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date removed;
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public long getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(long zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public long getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(long accountId) {
+        this.accountId = accountId;
+    }
+
+    public long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(long userId) {
+        this.userId = userId;
+    }
+
+    public Long getVmId() {
+        return vmId;
+    }
+
+    public void setVmId(Long vmId) {
+        this.vmId = vmId;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getVcenter() {
+        return vcenter;
+    }
+
+    public void setVcenter(String vcenter) {
+        this.vcenter = vcenter;
+    }
+
+    public String getDatacenter() {
+        return datacenter;
+    }
+
+    public void setDatacenter(String datacenter) {
+        this.datacenter = datacenter;
+    }
+
+    public String getSourceVMName() {
+        return sourceVMName;
+    }
+
+    public void setSourceVMName(String sourceVMName) {
+        this.sourceVMName = sourceVMName;
+    }
+
+    public long getConvertHostId() {
+        return convertHostId;
+    }
+
+    public void setConvertHostId(long convertHostId) {
+        this.convertHostId = convertHostId;
+    }
+
+    public long getImportHostId() {
+        return importHostId;
+    }
+
+    public void setImportHostId(long importHostId) {
+        this.importHostId = importHostId;
+    }
+
+    public Step getStep() {
+        return step;
+    }
+
+    public void setStep(Step step) {
+        this.step = step;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Long getDuration() {
+        return duration;
+    }
+
+    public void setDuration(Long duration) {
+        this.duration = duration;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getUpdated() {
+        return updated;
+    }
+
+    public void setUpdated(Date updated) {
+        this.updated = updated;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(Date removed) {
+        this.removed = removed;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java
new file mode 100644
index 00000000000..990dacb33fb
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java
@@ -0,0 +1,29 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package com.cloud.vm.dao;
+
+import com.cloud.utils.db.GenericDao;
+import com.cloud.vm.ImportVMTaskVO;
+
+import java.util.List;
+
+public interface ImportVMTaskDao extends GenericDao<ImportVMTaskVO, Long> {
+
+    List<ImportVMTaskVO> listImportVMTasks(Long zoneId, Long accountId, String vcenter, Long convertHostId, boolean showCompleted);
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java
new file mode 100644
index 00000000000..8bdae2e456a
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.vm.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.vm.ImportVMTaskVO;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+
+@Component
+public class ImportVMTaskDaoImpl extends GenericDaoBase<ImportVMTaskVO, Long> implements ImportVMTaskDao {
+
+    private SearchBuilder<ImportVMTaskVO> AllFieldsSearch;
+
+    public ImportVMTaskDaoImpl() {
+    }
+
+    @PostConstruct
+    void init() {
+        AllFieldsSearch = createSearchBuilder();
+        AllFieldsSearch.and("zoneId", AllFieldsSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("vcenter", AllFieldsSearch.entity().getVcenter(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("convertHostId", AllFieldsSearch.entity().getConvertHostId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.done();
+    }
+
+
+    @Override
+    public List<ImportVMTaskVO> listImportVMTasks(Long zoneId, Long accountId, String vcenter, Long convertHostId, boolean showCompleted) {
+        SearchCriteria<ImportVMTaskVO> sc = AllFieldsSearch.create();
+        if (zoneId != null) {
+            sc.setParameters("zoneId", zoneId);
+        }
+        if (accountId != null) {
+            sc.setParameters("accountId", accountId);
+        }
+        if (StringUtils.isNotBlank(vcenter)) {
+            sc.setParameters("vcenter", vcenter);
+        }
+        if (convertHostId != null) {
+            sc.setParameters("convertHostId", convertHostId);
+        }
+        return showCompleted ? listIncludingRemovedBy(sc) : listBy(sc);
+    }
+}
diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index 7e9d6c3b54f..0656d5e3c44 100644
--- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -309,4 +309,5 @@
   <bean id="gpuCardDaoImpl" class="com.cloud.gpu.dao.GpuCardDaoImpl" />
   <bean id="gpuDeviceDaoImpl" class="com.cloud.gpu.dao.GpuDeviceDaoImpl" />
   <bean id="vgpuProfileDaoImpl" class="com.cloud.gpu.dao.VgpuProfileDaoImpl" />
+  <bean id="importVMTaskDaoImpl" class="com.cloud.vm.dao.ImportVMTaskDaoImpl" />
 </beans>
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql
index 5b9b4272263..a14a9fef8ad 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql
@@ -48,5 +48,35 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_repository', 'cross_zone_inst
 UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%password%';
 UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%token%';
 
+-- VMware to KVM migration improvements
+CREATE TABLE IF NOT EXISTS `cloud`.`import_vm_task`(
+    `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+    `uuid` varchar(40),
+    `zone_id` bigint unsigned NOT NULL COMMENT 'Zone ID',
+    `account_id` bigint unsigned NOT NULL COMMENT 'Account ID',
+    `user_id` bigint unsigned NOT NULL COMMENT 'User ID',
+    `vm_id` bigint unsigned COMMENT 'VM ID',
+    `display_name` varchar(255) COMMENT 'Display VM Name',
+    `vcenter` varchar(255) COMMENT 'VCenter',
+    `datacenter` varchar(255) COMMENT 'VCenter Datacenter name',
+    `source_vm_name` varchar(255) COMMENT 'Source VM name on vCenter',
+    `convert_host_id` bigint unsigned COMMENT 'Convert Host ID',
+    `import_host_id` bigint unsigned COMMENT 'Import Host ID',
+    `step` varchar(20) NOT NULL COMMENT 'Importing VM Task Step',
+    `description` varchar(255) COMMENT 'Importing VM Task Description',
+    `duration` bigint unsigned COMMENT 'Duration in milliseconds for the completed tasks',
+    `created` datetime NOT NULL COMMENT 'date created',
+    `updated` datetime COMMENT 'date updated if not null',
+    `removed` datetime COMMENT 'date removed if not null',
+    PRIMARY KEY (`id`),
+    CONSTRAINT `fk_import_vm_task__zone_id` FOREIGN KEY `fk_import_vm_task__zone_id` (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE,
+    CONSTRAINT `fk_import_vm_task__account_id` FOREIGN KEY `fk_import_vm_task__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE,
+    CONSTRAINT `fk_import_vm_task__user_id` FOREIGN KEY `fk_import_vm_task__user_id` (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
+    CONSTRAINT `fk_import_vm_task__vm_id` FOREIGN KEY `fk_import_vm_task__vm_id` (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE,
+    CONSTRAINT `fk_import_vm_task__convert_host_id` FOREIGN KEY `fk_import_vm_task__convert_host_id` (`convert_host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
+    CONSTRAINT `fk_import_vm_task__import_host_id` FOREIGN KEY `fk_import_vm_task__import_host_id` (`import_host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
+    INDEX `i_import_vm_task__zone_id`(`zone_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('MaaS', 'Baremetal Extension for Canonical MaaS written in Python', 'MaaS/maas.py');
 CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('MaaS', 'orchestratorrequirespreparevm', 'true', 0);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index a86efeb8a1f..0aa094e56d9 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -882,6 +882,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
     protected StorageSubsystemCommandHandler storageHandler;
 
     private boolean convertInstanceVerboseMode = false;
+    private String[] convertInstanceEnv = null;
     protected boolean dpdkSupport = false;
     protected String dpdkOvsPath;
     protected String directDownloadTemporaryDownloadPath;
@@ -946,6 +947,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         return convertInstanceVerboseMode;
     }
 
+    public String[] getConvertInstanceEnv() {
+        return convertInstanceEnv;
+    }
+
     /**
      * Defines resource's public and private network interface according to what is configured in agent.properties.
      */
@@ -1146,6 +1151,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
 
         convertInstanceVerboseMode = BooleanUtils.isTrue(AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VIRTV2V_VERBOSE_ENABLED));
 
+        String convertEnvTmpDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CONVERT_ENV_TMPDIR);
+        String convertEnvVirtv2vTmpDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.CONVERT_ENV_VIRTV2V_TMPDIR);
+
+        setConvertInstanceEnv(convertEnvTmpDir, convertEnvVirtv2vTmpDir);
+
         pool = (String)params.get("pool");
         if (pool == null) {
             pool = "/root";
@@ -1422,6 +1432,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         return true;
     }
 
+    private void setConvertInstanceEnv(String convertEnvTmpDir, String convertEnvVirtv2vTmpDir) {
+        if (StringUtils.isAllBlank(convertEnvTmpDir, convertEnvVirtv2vTmpDir)) {
+            return;
+        }
+        if (StringUtils.isNotBlank(convertEnvTmpDir) && StringUtils.isNotBlank(convertEnvVirtv2vTmpDir)) {
+            convertInstanceEnv = new String[2];
+            convertInstanceEnv[0] = String.format("%s=%s", "TMPDIR", convertEnvTmpDir);
+            convertInstanceEnv[1] = String.format("%s=%s", "VIRT_V2V_TMPDIR", convertEnvVirtv2vTmpDir);
+        } else {
+            convertInstanceEnv = new String[1];
+            String key = StringUtils.isNotBlank(convertEnvTmpDir) ? "TMPDIR" : "VIRT_V2V_TMPDIR";
+            String value = StringUtils.isNotBlank(convertEnvTmpDir) ? convertEnvTmpDir : convertEnvVirtv2vTmpDir;
+            convertInstanceEnv[0] = String.format("%s=%s", key, value);
+        }
+    }
+
     /**
      * Parses a string containing whitespace-separated CPU feature names and converts it into a list.
      *
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
index abc408f20f6..93895349a6e 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java
@@ -20,6 +20,7 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
 
 import java.net.URLEncoder;
 import java.nio.charset.Charset;
+import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
@@ -57,11 +58,13 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
         Hypervisor.HypervisorType destinationHypervisorType = cmd.getDestinationHypervisorType();
         DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation();
         long timeout = (long) cmd.getWait() * 1000;
+        String extraParams = cmd.getExtraParams();
+        String originalVMName = cmd.getOriginalVMName(); // For logging purposes, as the sourceInstance may have been cloned
 
         if (cmd.getCheckConversionSupport() && !serverResource.hostSupportsInstanceConversion()) {
             String msg = String.format("Cannot convert the instance %s from VMware as the virt-v2v binary is not found. " +
                     "Please install virt-v2v%s on the host before attempting the instance conversion.", sourceInstanceName, serverResource.isUbuntuOrDebianHost()? ", nbdkit" : "");
-            logger.info(msg);
+            logger.info(String.format("(%s) %s", originalVMName, msg));
             return new Answer(cmd, false, msg);
         }
 
@@ -69,25 +72,25 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
             String err = destinationHypervisorType != Hypervisor.HypervisorType.KVM ?
                     String.format("The destination hypervisor type is %s, KVM was expected, cannot handle it", destinationHypervisorType) :
                     String.format("The source hypervisor type %s is not supported for KVM conversion", sourceHypervisorType);
-            logger.error(err);
+            logger.error(String.format("(%s) %s", originalVMName, err));
             return new Answer(cmd, false, err);
         }
 
         final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
         KVMStoragePool temporaryStoragePool = getTemporaryStoragePool(conversionTemporaryLocation, storagePoolMgr);
 
-        logger.info(String.format("Attempting to convert the instance %s from %s to KVM",
-                sourceInstanceName, sourceHypervisorType));
+        logger.info(String.format("(%s) Attempting to convert the instance %s from %s to KVM",
+                originalVMName, sourceInstanceName, sourceHypervisorType));
         final String temporaryConvertPath = temporaryStoragePool.getLocalPath();
 
         String ovfTemplateDirOnConversionLocation;
         String sourceOVFDirPath;
         boolean ovfExported = false;
         if (cmd.getExportOvfToConversionLocation()) {
-            String exportInstanceOVAUrl = getExportInstanceOVAUrl(sourceInstance);
+            String exportInstanceOVAUrl = getExportInstanceOVAUrl(sourceInstance, originalVMName);
             if (StringUtils.isBlank(exportInstanceOVAUrl)) {
                 String err = String.format("Couldn't export OVA for the VM %s, due to empty url", sourceInstanceName);
-                logger.error(err);
+                logger.error(String.format("(%s) %s", originalVMName, err));
                 return new Answer(cmd, false, err);
             }
 
@@ -98,10 +101,10 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
             ovfTemplateDirOnConversionLocation = UUID.randomUUID().toString();
             temporaryStoragePool.createFolder(ovfTemplateDirOnConversionLocation);
             sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
-            ovfExported = exportOVAFromVMOnVcenter(exportInstanceOVAUrl, sourceOVFDirPath, noOfThreads, timeout);
+            ovfExported = exportOVAFromVMOnVcenter(exportInstanceOVAUrl, sourceOVFDirPath, noOfThreads, originalVMName, timeout);
             if (!ovfExported) {
                 String err = String.format("Export OVA for the VM %s failed", sourceInstanceName);
-                logger.error(err);
+                logger.error(String.format("(%s) %s", originalVMName, err));
                 return new Answer(cmd, false, err);
             }
             sourceOVFDirPath = String.format("%s%s/", sourceOVFDirPath, sourceInstanceName);
@@ -110,38 +113,40 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
             sourceOVFDirPath = String.format("%s/%s/", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
         }
 
-        logger.info(String.format("Attempting to convert the OVF %s of the instance %s from %s to KVM", ovfTemplateDirOnConversionLocation, sourceInstanceName, sourceHypervisorType));
+        logger.info(String.format("(%s) Attempting to convert the OVF %s of the instance %s from %s to KVM",
+                originalVMName, ovfTemplateDirOnConversionLocation, sourceInstanceName, sourceHypervisorType));
+
         final String temporaryConvertUuid = UUID.randomUUID().toString();
         boolean verboseModeEnabled = serverResource.isConvertInstanceVerboseModeEnabled();
 
         boolean cleanupSecondaryStorage = false;
         try {
-            boolean result = performInstanceConversion(sourceOVFDirPath, temporaryConvertPath, temporaryConvertUuid,
-                    timeout, verboseModeEnabled);
+            boolean result = performInstanceConversion(originalVMName, sourceOVFDirPath, temporaryConvertPath, temporaryConvertUuid,
+                    timeout, verboseModeEnabled, extraParams, serverResource);
             if (!result) {
                 String err = String.format(
                         "The virt-v2v conversion for the OVF %s failed. Please check the agent logs " +
                                 "for the virt-v2v output. Please try on a different kvm host which " +
                                 "has a different virt-v2v version.",
                         ovfTemplateDirOnConversionLocation);
-                logger.error(err);
+                logger.error(String.format("(%s) %s", originalVMName, err));
                 return new Answer(cmd, false, err);
             }
             return new ConvertInstanceAnswer(cmd, temporaryConvertUuid);
         } catch (Exception e) {
             String error = String.format("Error converting instance %s from %s, due to: %s",
                     sourceInstanceName, sourceHypervisorType, e.getMessage());
-            logger.error(error, e);
+            logger.error(String.format("(%s) %s", originalVMName, error), e);
             cleanupSecondaryStorage = true;
             return new Answer(cmd, false, error);
         } finally {
             if (ovfExported && StringUtils.isNotBlank(ovfTemplateDirOnConversionLocation)) {
                 String sourceOVFDir = String.format("%s/%s", temporaryConvertPath, ovfTemplateDirOnConversionLocation);
-                logger.debug("Cleaning up exported OVA at dir " + sourceOVFDir);
+                logger.debug("({}) Cleaning up exported OVA at dir: {}", originalVMName, sourceOVFDir);
                 FileUtil.deletePath(sourceOVFDir);
             }
             if (cleanupSecondaryStorage && conversionTemporaryLocation instanceof NfsTO) {
-                logger.debug("Cleaning up secondary storage temporary location");
+                logger.debug("({}) Cleaning up secondary storage temporary location", originalVMName);
                 storagePoolMgr.deleteStoragePool(temporaryStoragePool.getType(), temporaryStoragePool.getUuid());
             }
         }
@@ -163,15 +168,15 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
                 supportedInstanceConvertSourceHypervisors.contains(sourceHypervisorType);
     }
 
-    private String getExportInstanceOVAUrl(RemoteInstanceTO sourceInstance) {
+    private String getExportInstanceOVAUrl(RemoteInstanceTO sourceInstance, String originalVMName) {
         String url = null;
         if (sourceInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware) {
-            url = getExportOVAUrlFromRemoteInstance(sourceInstance);
+            url = getExportOVAUrlFromRemoteInstance(sourceInstance, originalVMName);
         }
         return url;
     }
 
-    private String getExportOVAUrlFromRemoteInstance(RemoteInstanceTO vmwareInstance) {
+    private String getExportOVAUrlFromRemoteInstance(RemoteInstanceTO vmwareInstance, String originalVMName) {
         String vcenter = vmwareInstance.getVcenterHost();
         String username = vmwareInstance.getVcenterUsername();
         String password = vmwareInstance.getVcenterPassword();
@@ -182,7 +187,7 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
         String encodedUsername = encodeUsername(username);
         String encodedPassword = encodeUsername(password);
         if (StringUtils.isNotBlank(path)) {
-            logger.info("VM path: {}", path);
+            logger.info("({}) VM path: {}", originalVMName, path);
             return String.format("vi://%s:%s@%s/%s/%s/%s",
                     encodedUsername, encodedPassword, vcenter, datacenter, path, vm);
         }
@@ -201,7 +206,7 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
     private boolean exportOVAFromVMOnVcenter(String vmExportUrl,
                                              String targetOvfDir,
                                              int noOfThreads,
-                                             long timeout) {
+                                             String originalVMName, long timeout) {
         Script script = new Script("ovftool", timeout, logger);
         script.add("--noSSLVerify");
         if (noOfThreads > 1) {
@@ -210,17 +215,18 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
         script.add(vmExportUrl);
         script.add(targetOvfDir);
 
-        String logPrefix = "export ovf";
+        String logPrefix = String.format("(%s) export ovf", originalVMName);
         OutputInterpreter.LineByLineOutputLogger outputLogger = new OutputInterpreter.LineByLineOutputLogger(logger, logPrefix);
         script.execute(outputLogger);
         int exitValue = script.getExitValue();
         return exitValue == 0;
     }
 
-    protected boolean performInstanceConversion(String sourceOVFDirPath,
+    protected boolean performInstanceConversion(String originalVMName, String sourceOVFDirPath,
                                                 String temporaryConvertFolder,
                                                 String temporaryConvertUuid,
-                                                long timeout, boolean verboseModeEnabled) {
+                                                long timeout, boolean verboseModeEnabled, String extraParams,
+                                                LibvirtComputingResource serverResource) {
         Script script = new Script("virt-v2v", timeout, logger);
         script.add("--root", "first");
         script.add("-i", "ova");
@@ -232,14 +238,33 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
         if (verboseModeEnabled) {
             script.add("-v");
         }
+        if (StringUtils.isNotBlank(extraParams)) {
+            addExtraParamsToScript(extraParams, script);
+        }
 
-        String logPrefix = String.format("virt-v2v ovf source: %s progress", sourceOVFDirPath);
+        String logPrefix = String.format("(%s) virt-v2v ovf source: %s progress", originalVMName, sourceOVFDirPath);
         OutputInterpreter.LineByLineOutputLogger outputLogger = new OutputInterpreter.LineByLineOutputLogger(logger, logPrefix);
         script.execute(outputLogger);
         int exitValue = script.getExitValue();
         return exitValue == 0;
     }
 
+    protected void addExtraParamsToScript(String extraParams, Script script) {
+        List<String> separatedArgs = Arrays.asList(extraParams.split(" "));
+        int i = 0;
+        while (i < separatedArgs.size()) {
+            String current = separatedArgs.get(i);
+            String next = (i + 1) < separatedArgs.size() ? separatedArgs.get(i + 1) : null;
+            if (next == null || next.startsWith("-")) {
+                script.add(current);
+                i = i + 1;
+            } else {
+                script.add(current, next);
+                i = i + 2;
+            }
+        }
+    }
+
     protected String encodeUsername(String username) {
         return URLEncoder.encode(username, Charset.defaultCharset());
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapper.java
index 0eec15cf733..5602da15679 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtImportConvertedInstanceCommandWrapper.java
@@ -67,6 +67,7 @@ public class LibvirtImportConvertedInstanceCommandWrapper extends CommandWrapper
         List<String> destinationStoragePools = cmd.getDestinationStoragePools();
         DataStoreTO conversionTemporaryLocation = cmd.getConversionTemporaryLocation();
         final String temporaryConvertUuid = cmd.getTemporaryConvertUuid();
+        final boolean forceConvertToPool = cmd.isForceConvertToPool();
 
         final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
         KVMStoragePool temporaryStoragePool = getTemporaryStoragePool(conversionTemporaryLocation, storagePoolMgr);
@@ -80,13 +81,18 @@ public class LibvirtImportConvertedInstanceCommandWrapper extends CommandWrapper
                     getTemporaryDisksWithPrefixFromTemporaryPool(temporaryStoragePool, temporaryConvertPath, temporaryConvertUuid) :
                     getTemporaryDisksFromParsedXml(temporaryStoragePool, xmlParser, convertedBasePath);
 
-            List<KVMPhysicalDisk> destinationDisks = moveTemporaryDisksToDestination(temporaryDisks,
-                    destinationStoragePools, storagePoolMgr);
-
-            cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid);
+            List<KVMPhysicalDisk> disks = null;
+            if (forceConvertToPool) {
+                // Force flag to use the conversion path, no need to move disks
+                disks = temporaryDisks;
+            } else {
+                disks = moveTemporaryDisksToDestination(temporaryDisks,
+                        destinationStoragePools, storagePoolMgr);
+                cleanupDisksAndDomainFromTemporaryLocation(temporaryDisks, temporaryStoragePool, temporaryConvertUuid);
+            }
 
             UnmanagedInstanceTO convertedInstanceTO = getConvertedUnmanagedInstance(temporaryConvertUuid,
-                    destinationDisks, xmlParser);
+                    disks, xmlParser);
             return new ImportConvertedInstanceAnswer(cmd, convertedInstanceTO);
         } catch (Exception e) {
             String error = String.format("Error converting instance %s from %s, due to: %s",
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java
index b369cf25f3d..4d55ac2bc73 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java
@@ -128,6 +128,7 @@ public class LibvirtConvertInstanceCommandWrapperTest {
         Mockito.when(cmd.getWait()).thenReturn(14400);
         Mockito.when(cmd.getConversionTemporaryLocation()).thenReturn(secondaryDataStore);
         Mockito.when(cmd.getCheckConversionSupport()).thenReturn(checkConversionSupport);
+        Mockito.when(cmd.getOriginalVMName()).thenReturn(vmName);
         return cmd;
     }
 
@@ -166,8 +167,26 @@ public class LibvirtConvertInstanceCommandWrapperTest {
 
             Answer answer = convertInstanceCommandWrapper.execute(cmd, libvirtComputingResourceMock);
             Assert.assertFalse(answer.getResult());
-            Mockito.verify(convertInstanceCommandWrapper).performInstanceConversion(Mockito.anyString(),
-                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean());
+            Mockito.verify(convertInstanceCommandWrapper).performInstanceConversion(Mockito.anyString(), Mockito.anyString(),
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean(), Mockito.nullable(String.class), Mockito.any(LibvirtComputingResource.class));
         }
     }
+
+    @Test
+    public void testAddExtraParamsToScriptSameKeysAndValues() {
+        Script script = Mockito.mock(Script.class);
+        String extraParams = "--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254";
+        convertInstanceCommandWrapper.addExtraParamsToScript(extraParams, script);
+        Mockito.verify(script).add("--mac", "00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254");
+    }
+
+    @Test
+    public void testAddExtraParamsToScriptDifferentArgs() {
+        Script script = Mockito.mock(Script.class);
+        String extraParams = "--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x -v";
+        convertInstanceCommandWrapper.addExtraParamsToScript(extraParams, script);
+        Mockito.verify(script).add("--mac", "00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254");
+        Mockito.verify(script).add("-x");
+        Mockito.verify(script).add("-v");
+    }
 }
diff --git a/server/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManagerImpl.java
new file mode 100644
index 00000000000..fa36627e864
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManagerImpl.java
@@ -0,0 +1,225 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.vm;
+
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DataCenterVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountService;
+import com.cloud.utils.DateUtil;
+import com.cloud.vm.ImportVMTaskVO;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.dao.ImportVMTaskDao;
+import com.cloud.vm.dao.UserVmDao;
+import org.apache.cloudstack.api.command.admin.vm.ListImportVMTasksCmd;
+import org.apache.cloudstack.api.response.ImportVMTaskResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.inject.Inject;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import static org.apache.cloudstack.vm.ImportVmTask.Step.CloningInstance;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.Completed;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.ConvertingInstance;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.Importing;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.Prepare;
+
+public class ImportVmTasksManagerImpl implements ImportVmTasksManager {
+
+    protected Logger logger = LogManager.getLogger(ImportVmTasksManagerImpl.class);
+
+    @Inject
+    private ImportVMTaskDao importVMTaskDao;
+    @Inject
+    private DataCenterDao dataCenterDao;
+    @Inject
+    private AccountService accountService;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private UserVmDao userVmDao;
+
+    public ImportVmTasksManagerImpl() {
+    }
+
+    @Override
+    public ListResponse<ImportVMTaskResponse> listImportVMTasks(ListImportVMTasksCmd cmd) {
+        Long zoneId = cmd.getZoneId();
+        Long accountId = cmd.getAccountId();
+        String vcenter = cmd.getVcenter();
+        Long convertHostId = cmd.getConvertHostId();
+        boolean listAll = cmd.isListAll();
+        boolean showCompleted = cmd.isShowCompleted();
+
+        List<ImportVMTaskVO> tasks;
+        if (listAll) {
+            tasks = importVMTaskDao.listAll();
+        } else {
+            tasks = importVMTaskDao.listImportVMTasks(zoneId, accountId, vcenter, convertHostId, showCompleted);
+        }
+
+        List<ImportVMTaskResponse> responses = new ArrayList<>();
+        for (ImportVMTaskVO task : tasks) {
+            responses.add(createImportVMTaskResponse(task));
+        }
+        ListResponse<ImportVMTaskResponse> listResponses = new ListResponse<>();
+        listResponses.setResponses(responses, responses.size());
+        return listResponses;
+    }
+
+    @Override
+    public ImportVmTask createImportVMTaskRecord(DataCenter zone, Account owner, long userId, String displayName, String vcenter, String datacenterName, String sourceVMName, Host convertHost, Host importHost) {
+        logger.debug("Creating import VM task entry for VM: {} for account {} on zone {} " +
+                        "from the vCenter: {} / datacenter: {} / source VM: {}",
+                sourceVMName, owner.getAccountName(), zone.getName(), displayName, vcenter, datacenterName);
+        ImportVMTaskVO importVMTaskVO = new ImportVMTaskVO(zone.getId(), owner.getAccountId(), userId, displayName,
+                vcenter, datacenterName, sourceVMName, convertHost.getId(), importHost.getId());
+        return importVMTaskDao.persist(importVMTaskVO);
+    }
+
+    private String getStepDescription(ImportVMTaskVO importVMTaskVO, Host convertHost, Host importHost,
+                                      ImportVMTaskVO.Step step, Date updatedDate) {
+        String sourceVMName = importVMTaskVO.getSourceVMName();
+        String vcenter = importVMTaskVO.getVcenter();
+        String datacenter = importVMTaskVO.getDatacenter();
+
+        StringBuilder stringBuilder = new StringBuilder();
+        if (Completed == step) {
+            stringBuilder.append("Completed at ").append(DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), updatedDate));
+        } else {
+            stringBuilder.append(String.format("[%s] ", DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), updatedDate)));
+            if (CloningInstance == step) {
+                stringBuilder.append(String.format("Cloning source instance: %s on vCenter: %s / datacenter: %s", sourceVMName, vcenter, datacenter));
+            } else if (ConvertingInstance == step) {
+                stringBuilder.append(String.format("Converting the cloned VMware instance to a KVM instance on the host: %s", convertHost.getName()));
+            } else if (Importing == step) {
+                stringBuilder.append(String.format("Importing the converted KVM instance on the host: %s", importHost.getName()));
+            } else if (Prepare == step) {
+                stringBuilder.append("Preparing to convert Vmware instance");
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+    @Override
+    public void updateImportVMTaskStep(ImportVmTask importVMTask, DataCenter zone, Account owner, Host convertHost,
+                                       Host importHost, Long vmId, ImportVmTask.Step step) {
+        ImportVMTaskVO importVMTaskVO = (ImportVMTaskVO) importVMTask;
+        logger.debug("Updating import VM task entry for VM: {} for account {} on zone {} " +
+                        "from the vCenter: {} / datacenter: {} / source VM: {} to step: {}",
+                importVMTaskVO.getSourceVMName(), owner.getAccountName(), zone.getName(), importVMTaskVO.getDisplayName(),
+                importVMTaskVO.getVcenter(), importVMTaskVO.getDatacenter(), step);
+        Date updatedDate = DateUtil.now();
+        String description = getStepDescription(importVMTaskVO, convertHost, importHost, step, updatedDate);
+        importVMTaskVO.setStep(step);
+        importVMTaskVO.setDescription(description);
+        importVMTaskVO.setUpdated(updatedDate);
+        if (Completed == step) {
+            Duration duration = Duration.between(importVMTaskVO.getCreated().toInstant(), updatedDate.toInstant());
+            importVMTaskVO.setDuration(duration.toMillis());
+            importVMTaskVO.setVmId(vmId);
+        }
+        importVMTaskDao.update(importVMTaskVO.getId(), importVMTaskVO);
+    }
+
+    @Override
+    public boolean removeImportVMTask(long taskId) {
+        return importVMTaskDao.remove(taskId);
+    }
+
+    private ImportVMTaskResponse createImportVMTaskResponse(ImportVMTaskVO task) {
+        ImportVMTaskResponse response = new ImportVMTaskResponse();
+        DataCenterVO zone = dataCenterDao.findById(task.getZoneId());
+        if (zone != null) {
+            response.setZoneId(zone.getUuid());
+            response.setZoneName(zone.getName());
+        }
+        Account account = accountService.getAccount(task.getAccountId());
+        if (account != null) {
+            response.setAccountId(account.getUuid());
+            response.setAccountName(account.getAccountName());
+        }
+        response.setVcenter(task.getVcenter());
+        response.setDatacenterName(task.getDatacenter());
+        response.setSourceVMName(task.getSourceVMName());
+        response.setDisplayName(task.getDisplayName());
+        response.setStep(getStepDisplayField(task.getStep()));
+        response.setDescription(task.getDescription());
+
+        Date updated = task.getUpdated();
+        Date currentDate = new Date();
+        if (updated != null && Completed != task.getStep()) {
+            Duration stepDuration = Duration.between(updated.toInstant(), currentDate.toInstant());
+            response.setStepDuration(getDurationDisplay(stepDuration.toMillis()));
+        }
+        if (Completed == task.getStep()) {
+            response.setStepDuration(getDurationDisplay(task.getDuration()));
+        } else {
+            Duration totalDuration = Duration.between(task.getCreated().toInstant(), currentDate.toInstant());
+            response.setDuration(getDurationDisplay(totalDuration.toMillis()));
+        }
+        HostVO host = hostDao.findById(task.getConvertHostId());
+        if (host != null) {
+            response.setConvertInstanceHostId(host.getUuid());
+            response.setConvertInstanceHostName(host.getName());
+        }
+        if (task.getVmId() != null) {
+            UserVmVO userVm = userVmDao.findById(task.getVmId());
+            response.setVirtualMachineId(userVm.getUuid());
+        }
+        response.setCreated(task.getCreated());
+        response.setLastUpdated(task.getUpdated());
+        response.setObjectName("importvmtask");
+        return response;
+    }
+
+    protected String getStepDisplayField(ImportVMTaskVO.Step step) {
+        int totalSteps = ImportVMTaskVO.Step.values().length;
+        return String.format("[%s/%s] %s", step.ordinal() + 1, totalSteps, step.name());
+    }
+
+    protected static String getDurationDisplay(Long durationMs) {
+        if (durationMs == null) {
+            return null;
+        }
+        long hours = durationMs / (1000 * 60 * 60);
+        long minutes = (durationMs / (1000 * 60)) % 60;
+        long seconds = (durationMs / 1000) % 60;
+
+        StringBuilder result = new StringBuilder();
+        if (hours > 0) {
+            result.append(String.format("%s hs ", hours));
+        }
+        if (minutes > 0) {
+            result.append(String.format("%s min ", minutes));
+        }
+        if (seconds > 0) {
+            result.append(String.format("%s secs", seconds));
+        }
+        return result.toString();
+    }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
index 187b315dd35..51623b56ec8 100644
--- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java
@@ -122,6 +122,7 @@ import com.cloud.user.dao.UserDao;
 import com.cloud.uservm.UserVm;
 import com.cloud.utils.LogUtils;
 import com.cloud.utils.Pair;
+import com.cloud.utils.UuidUtils;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
@@ -150,6 +151,7 @@ import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.command.admin.vm.ImportUnmanagedInstanceCmd;
 import org.apache.cloudstack.api.command.admin.vm.ImportVmCmd;
+import org.apache.cloudstack.api.command.admin.vm.ListImportVMTasksCmd;
 import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd;
 import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd;
 import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd;
@@ -173,6 +175,8 @@ import org.apache.cloudstack.storage.volume.VolumeOnStorageTO;
 import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -180,7 +184,9 @@ import org.apache.logging.log4j.Logger;
 import javax.inject.Inject;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -190,6 +196,10 @@ import java.util.stream.Collectors;
 
 import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS;
 import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.CloningInstance;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.Completed;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.ConvertingInstance;
+import static org.apache.cloudstack.vm.ImportVmTask.Step.Importing;
 
 public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
     public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso";
@@ -198,6 +208,28 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
     private static final List<Hypervisor.HypervisorType> importUnmanagedInstancesSupportedHypervisors =
             Arrays.asList(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM);
 
+    private static final List<Storage.StoragePoolType> forceConvertToPoolAllowedTypes =
+            Arrays.asList(Storage.StoragePoolType.NetworkFilesystem, Storage.StoragePoolType.Filesystem,
+                    Storage.StoragePoolType.SharedMountPoint);
+
+    ConfigKey<Boolean> ConvertVmwareInstanceToKvmExtraParamsAllowed = new ConfigKey<>(Boolean.class,
+            "convert.vmware.instance.to.kvm.extra.params.allowed",
+            "Advanced",
+            "false",
+            "Disabled by default. If enabled, allows extra parameters to be passed to the virt-v2v binary on KVM conversion hosts",
+            true,
+            ConfigKey.Scope.Global,
+            null);
+
+    ConfigKey<String> ConvertVmwareInstanceToKvmExtraParamsAllowedList = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
+            String.class,
+            "convert.vmware.instance.to.kvm.extra.params.allowed.list",
+            "",
+            "Comma separated list of allowed extra parameters to be passed to the virt-v2v binary on KVM conversion hosts",
+            true,
+            ConfigKey.Kind.CSV,
+            null);
+
     @Inject
     private AgentManager agentManager;
     @Inject
@@ -282,6 +314,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
     private ImageStoreDao imageStoreDao;
     @Inject
     private DataStoreManager dataStoreManager;
+    @Inject
+    private ImportVmTasksManager importVmTasksManager;
 
     protected Gson gson;
 
@@ -557,11 +591,12 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         }
 
         if (storagePool == null) {
-            List<StoragePoolVO> pools = primaryDataStoreDao.listPoolsByCluster(cluster.getId());
+            Set<StoragePoolVO> pools = new HashSet<>(primaryDataStoreDao.listPoolsByCluster(cluster.getId()));
             pools.addAll(primaryDataStoreDao.listByDataCenterId(zone.getId()));
+            boolean isNameUuid = StringUtils.isNotBlank(dsName) && UuidUtils.isUuid(dsName);
             for (StoragePool pool : pools) {
                 String searchPoolParam = StringUtils.isNotBlank(dsPath) ? dsPath : dsName;
-                if (StringUtils.contains(pool.getPath(), searchPoolParam) &&
+                if ((StringUtils.contains(pool.getPath(), searchPoolParam) || isNameUuid && pool.getUuid().equals(dsName)) &&
                         volumeApiService.doesStoragePoolSupportDiskOffering(pool, diskOffering)) {
                     storagePool = pool;
                     break;
@@ -1418,6 +1453,33 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         return responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVm).get(0);
     }
 
+    protected void checkExtraParamsAllowed(String extraParams) {
+        if (StringUtils.isBlank(extraParams)) {
+            return;
+        }
+        if (BooleanUtils.isFalse(ConvertVmwareInstanceToKvmExtraParamsAllowed.value())) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Extra parameters for Vmware to KVM conversion are disabled by the administrator");
+        }
+        String allowedParamsStr = ConvertVmwareInstanceToKvmExtraParamsAllowedList.value();
+        if (StringUtils.isBlank(allowedParamsStr)) {
+            throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                    "Extra parameters for Vmware to KVM conversion are enabled but the allowed list of parameters is empty");
+        }
+        List<String> allowedParams = Arrays.asList(allowedParamsStr.split(","));
+        List<String> sanitizedParams = Arrays.asList(extraParams.split(" "))
+                .stream()
+                .filter(x -> x.startsWith("-"))
+                .map(s -> s.replaceFirst("^-+", "").trim()) //Remove the starting hyphens as in --X or -x
+                .collect(Collectors.toList());
+        for (String param : sanitizedParams) {
+            if (!allowedParams.contains(param)) {
+                throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
+                        String.format("The parameter %s is not allowed by the administrator", param));
+            }
+        }
+    }
+
     private long getUserIdForImportInstance(Account owner) {
         long userId = CallContext.current().getCallingUserId();
         List<UserVO> userVOs = userDao.listByAccount(owner.getAccountId());
@@ -1513,6 +1575,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         String source = cmd.getImportSource().toUpperCase();
         ImportSource importSource = Enum.valueOf(ImportSource.class, source);
         if (ImportSource.VMWARE == importSource  || ImportSource.UNMANAGED == importSource) {
+            checkExtraParamsAllowed(cmd.getExtraParams());
             return baseImportInstance(cmd);
         } else {
             return importKvmInstance(cmd);
@@ -1621,6 +1684,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         Long convertInstanceHostId = cmd.getConvertInstanceHostId();
         Long importInstanceHostId = cmd.getImportInstanceHostId();
         Long convertStoragePoolId = cmd.getConvertStoragePoolId();
+        String extraParams = cmd.getExtraParams();
+        boolean forceConvertToPool = cmd.getForceConvertToPool();
 
         if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
@@ -1631,6 +1696,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
                     "Please set all the information for a vCenter IP/Name, datacenter, username and password");
         }
 
+        checkConversionStoragePool(convertStoragePoolId, forceConvertToPool);
+
+        checkExtraParamsAllowed(extraParams);
+
         if (existingVcenterId != null) {
             VmwareDatacenterVO existingDC = vmwareDatacenterDao.findById(existingVcenterId);
             if (existingDC == null) {
@@ -1648,6 +1717,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         UnmanagedInstanceTO sourceVMwareInstance = null;
         DataStoreTO temporaryConvertLocation = null;
         String ovfTemplateOnConvertLocation = null;
+        ImportVmTask importVMTask = null;
         try {
             HostVO convertHost = selectKVMHostForConversionInCluster(destinationCluster, convertInstanceHostId);
             HostVO importHost = selectKVMHostForImportingInCluster(destinationCluster, importInstanceHostId);
@@ -1656,12 +1726,21 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
                     "instance {} from VMware to KVM ", convertHost, sourceVMName);
 
             temporaryConvertLocation = selectInstanceConversionTemporaryLocation(
-                    destinationCluster, convertHost, convertStoragePoolId);
-            List<StoragePoolVO> convertStoragePools = findInstanceConversionStoragePoolsInCluster(destinationCluster, serviceOffering, dataDiskOfferingMap);
+                    destinationCluster, convertHost, importHost, convertStoragePoolId, forceConvertToPool);
+            List<StoragePoolVO> convertStoragePools = findInstanceConversionDestinationStoragePoolsInCluster(destinationCluster, serviceOffering, dataDiskOfferingMap, temporaryConvertLocation, forceConvertToPool);
+
             long importStartTime = System.currentTimeMillis();
+            importVMTask = importVmTasksManager.createImportVMTaskRecord(zone, owner, userId, displayName, vcenter, datacenterName, sourceVMName,
+                    convertHost, importHost);
+            importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, CloningInstance);
+
+            // sourceVMwareInstance could be a cloned instance from sourceVMName, of the sourceVMName itself if its powered off.
+            // isClonedInstance indicates if the VM is a clone of sourceVMName
+
             Pair<UnmanagedInstanceTO, Boolean> sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName);
             sourceVMwareInstance = sourceInstanceDetails.first();
             isClonedInstance = sourceInstanceDetails.second();
+
             boolean isWindowsVm = sourceVMwareInstance.getOperatingSystem().toLowerCase().contains("windows");
             if (isWindowsVm) {
                 checkConversionSupportOnHost(convertHost, sourceVMName, true);
@@ -1672,22 +1751,25 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
             if (cmd.getForceMsToImportVmFiles() || !conversionSupportAnswer.isOvfExportSupported()) {
                 // Uses MS for OVF export to temporary conversion location
                 int noOfThreads = UnmanagedVMsManager.ThreadsOnMSToImportVMwareVMFiles.value();
+                importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance);
                 ovfTemplateOnConvertLocation = createOvfTemplateOfSourceVmwareUnmanagedInstance(
                         vcenter, datacenterName, username, password, clusterName, sourceHostName,
                         sourceVMwareInstance.getName(), temporaryConvertLocation, noOfThreads);
                 convertedInstance = convertVmwareInstanceToKVMWithOVFOnConvertLocation(sourceVMName,
                         sourceVMwareInstance, convertHost, importHost, convertStoragePools,
                         serviceOffering, dataDiskOfferingMap, temporaryConvertLocation,
-                        ovfTemplateOnConvertLocation);
+                        ovfTemplateOnConvertLocation, forceConvertToPool, extraParams);
             } else {
                 // Uses KVM Host for OVF export to temporary conversion location, through ovftool
+                importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, ConvertingInstance);
                 convertedInstance = convertVmwareInstanceToKVMAfterExportingOVFToConvertLocation(
                         sourceVMName, sourceVMwareInstance, convertHost, importHost,
                         convertStoragePools, serviceOffering, dataDiskOfferingMap,
-                        temporaryConvertLocation, vcenter, username, password, datacenterName);
+                        temporaryConvertLocation, vcenter, username, password, datacenterName, forceConvertToPool, extraParams);
             }
 
             sanitizeConvertedInstance(convertedInstance, sourceVMwareInstance);
+            importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, null, Importing);
             UserVm userVm = importVirtualMachineInternal(convertedInstance, null, zone, destinationCluster, null,
                     template, displayName, hostName, caller, owner, userId,
                     serviceOffering, dataDiskOfferingMap,
@@ -1696,6 +1778,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
             long timeElapsedInSecs = (System.currentTimeMillis() - importStartTime) / 1000;
             logger.debug(String.format("VMware VM %s imported successfully to CloudStack instance %s (%s), Time taken: %d secs, OVF files imported from %s, Source VMware VM details - OS: %s, PowerState: %s, Disks: %s, NICs: %s",
                     sourceVMName, displayName, displayName, timeElapsedInSecs, (ovfTemplateOnConvertLocation != null)? "MS" : "KVM Host", sourceVMwareInstance.getOperatingSystem(), sourceVMwareInstance.getPowerState(), sourceVMwareInstance.getDisks(), sourceVMwareInstance.getNics()));
+            importVmTasksManager.updateImportVMTaskStep(importVMTask, zone, owner, convertHost, importHost, userVm.getId(), Completed);
+            importVmTasksManager.removeImportVMTask(importVMTask.getId());
             return userVm;
         } catch (CloudRuntimeException e) {
             logger.error(String.format("Error importing VM: %s", e.getMessage()), e);
@@ -1712,6 +1796,30 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         }
     }
 
+    /**
+     * Check whether the conversion storage pool exists and is suitable for the conversion or not.
+     * Secondary storage is only allowed when forceConvertToPool is false.
+     * @param convertStoragePoolId the ID of the storage pool (primary or secondary)
+     * @param forceConvertToPool when true, only primary storage pool must be allowed
+     * @throws CloudRuntimeException in case these requirements are not met
+     */
+    protected void checkConversionStoragePool(Long convertStoragePoolId, boolean forceConvertToPool) {
+        if (forceConvertToPool && convertStoragePoolId == null) {
+            String msg = "The parameter forceconverttopool is set to true, but a primary storage pool has not been provided for conversion";
+            logFailureAndThrowException(msg);
+        }
+        if (convertStoragePoolId != null) {
+            StoragePoolVO selectedStoragePool = primaryDataStoreDao.findById(convertStoragePoolId);
+            if (selectedStoragePool == null) {
+                logFailureAndThrowException(String.format("Cannot find a storage pool with ID %s", convertStoragePoolId));
+            }
+            if (forceConvertToPool && !forceConvertToPoolAllowedTypes.contains(selectedStoragePool.getPoolType())) {
+                logFailureAndThrowException(String.format("The selected storage pool %s does not support direct conversion " +
+                        "as its type %s", selectedStoragePool.getName(), selectedStoragePool.getPoolType().name()));
+            }
+        }
+    }
+
     private void checkNetworkingBeforeConvertingVmwareInstance(DataCenter zone, Account owner, String displayName,
                                                                String hostName, UnmanagedInstanceTO sourceVMwareInstance,
                                                                Map<String, Long> nicNetworkMap,
@@ -1953,20 +2061,25 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
             String sourceVM, UnmanagedInstanceTO sourceVMwareInstance, HostVO convertHost,
             HostVO importHost, List<StoragePoolVO> convertStoragePools,
             ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
-            DataStoreTO temporaryConvertLocation, String ovfTemplateDirConvertLocation
-    ) {
-            logger.debug("Delegating the conversion of instance {} from VMware to KVM to the host {} using OVF {} on conversion datastore",
-                    sourceVM, convertHost, ovfTemplateDirConvertLocation);
+            DataStoreTO temporaryConvertLocation, String ovfTemplateDirConvertLocation,
+            boolean forceConvertToPool, String extraParams) {
+
+        logger.debug("Delegating the conversion of instance {} from VMware to KVM to the host {} using OVF {} on conversion datastore",
+                sourceVM, convertHost, ovfTemplateDirConvertLocation);
 
         RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(sourceVM);
         List<String> destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks(), serviceOffering, dataDiskOfferingMap);
         ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO,
-                Hypervisor.HypervisorType.KVM, temporaryConvertLocation, ovfTemplateDirConvertLocation, false, false);
+                Hypervisor.HypervisorType.KVM, temporaryConvertLocation,
+                ovfTemplateDirConvertLocation, false, false, sourceVM);
+        if (StringUtils.isNotBlank(extraParams)) {
+            cmd.setExtraParams(extraParams);
+        }
         int timeoutSeconds = UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60;
         cmd.setWait(timeoutSeconds);
 
         return convertAndImportToKVM(cmd, convertHost, importHost, sourceVM,
-                remoteInstanceTO, destinationStoragePools, temporaryConvertLocation);
+                remoteInstanceTO, destinationStoragePools, temporaryConvertLocation, forceConvertToPool);
     }
 
     private UnmanagedInstanceTO convertVmwareInstanceToKVMAfterExportingOVFToConvertLocation(
@@ -1974,14 +2087,13 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
             HostVO importHost, List<StoragePoolVO> convertStoragePools,
             ServiceOfferingVO serviceOffering, Map<String, Long> dataDiskOfferingMap,
             DataStoreTO temporaryConvertLocation, String vcenterHost, String vcenterUsername,
-            String vcenterPassword, String datacenterName
-    ) {
+            String vcenterPassword, String datacenterName, boolean forceConvertToPool, String extraParams) {
         logger.debug("Delegating the conversion of instance {} from VMware to KVM to the host {} after OVF export through ovftool", sourceVM, convertHost);
 
         RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(sourceVMwareInstance.getName(), sourceVMwareInstance.getPath(), vcenterHost, vcenterUsername, vcenterPassword, datacenterName);
         List<String> destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks(), serviceOffering, dataDiskOfferingMap);
         ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO,
-                Hypervisor.HypervisorType.KVM, temporaryConvertLocation, null, false, true);
+                Hypervisor.HypervisorType.KVM, temporaryConvertLocation, null, false, true, sourceVM);
         int timeoutSeconds = UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60;
         cmd.setWait(timeoutSeconds);
         int noOfThreads = UnmanagedVMsManager.ThreadsOnKVMHostToImportVMwareVMFiles.value();
@@ -1990,16 +2102,19 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
             noOfThreads = sourceVMwareInstance.getDisks().size();
         }
         cmd.setThreadsCountToExportOvf(noOfThreads);
-
+        if (StringUtils.isNotBlank(extraParams)) {
+            cmd.setExtraParams(extraParams);
+        }
         return convertAndImportToKVM(cmd, convertHost, importHost, sourceVM,
-                remoteInstanceTO, destinationStoragePools, temporaryConvertLocation);
+                remoteInstanceTO, destinationStoragePools, temporaryConvertLocation, forceConvertToPool);
     }
 
     private UnmanagedInstanceTO convertAndImportToKVM(ConvertInstanceCommand convertInstanceCommand, HostVO convertHost, HostVO importHost,
                                                       String sourceVM,
                                                       RemoteInstanceTO remoteInstanceTO,
                                                       List<String> destinationStoragePools,
-                                                      DataStoreTO temporaryConvertLocation) {
+                                                      DataStoreTO temporaryConvertLocation,
+                                                      boolean forceConvertToPool) {
         Answer convertAnswer;
         try {
             convertAnswer = agentManager.send(convertHost.getId(), convertInstanceCommand);
@@ -2021,7 +2136,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         try {
             ImportConvertedInstanceCommand importCmd = new ImportConvertedInstanceCommand(
                     remoteInstanceTO, destinationStoragePools, temporaryConvertLocation,
-                    ((ConvertInstanceAnswer)convertAnswer).getTemporaryConvertUuid());
+                    ((ConvertInstanceAnswer)convertAnswer).getTemporaryConvertUuid(), forceConvertToPool);
             importAnswer = agentManager.send(importHost.getId(), importCmd);
         } catch (AgentUnavailableException | OperationTimedoutException e) {
             String err = String.format(
@@ -2042,17 +2157,23 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         return ((ImportConvertedInstanceAnswer) importAnswer).getConvertedInstance();
     }
 
-    private List<StoragePoolVO> findInstanceConversionStoragePoolsInCluster(
+    private List<StoragePoolVO> findInstanceConversionDestinationStoragePoolsInCluster(
             Cluster destinationCluster, ServiceOfferingVO serviceOffering,
-            Map<String, Long> dataDiskOfferingMap
-    ) {
-        List<StoragePoolVO> pools = new ArrayList<>();
-        pools.addAll(primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem));
-        pools.addAll(primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getDataCenterId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem));
-        if (pools.isEmpty()) {
-            String msg = String.format("Cannot find suitable storage pools in the cluster %s for the conversion", destinationCluster.getName());
-            logger.error(msg);
-            throw new CloudRuntimeException(msg);
+            Map<String, Long> dataDiskOfferingMap,
+            DataStoreTO temporaryConvertLocation, boolean forceConvertToPool) {
+        List<StoragePoolVO> poolsList;
+        if (!forceConvertToPool) {
+            Set<StoragePoolVO> pools = new HashSet<>(primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem));
+            pools.addAll(primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getDataCenterId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem));
+            if (pools.isEmpty()) {
+                String msg = String.format("Cannot find suitable storage pools in the cluster %s for the conversion", destinationCluster.getName());
+                logger.error(msg);
+                throw new CloudRuntimeException(msg);
+            }
+            poolsList = new ArrayList<>(pools);
+        } else {
+            DataStore dataStore = dataStoreManager.getDataStore(temporaryConvertLocation.getUuid(), temporaryConvertLocation.getRole());
+            poolsList = Collections.singletonList(primaryDataStoreDao.findById(dataStore.getId()));
         }
 
         if (serviceOffering.getDiskOfferingId() != null) {
@@ -2062,7 +2183,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
                 logger.error(msg);
                 throw new CloudRuntimeException(msg);
             }
-            if (getStoragePoolWithTags(pools, diskOffering.getTags()) == null) {
+            if (getStoragePoolWithTags(poolsList, diskOffering.getTags()) == null) {
                 String msg = String.format("Cannot find suitable storage pool for disk offering %s that belongs to the service offering %s", diskOffering.getName(), serviceOffering.getName());
                 logger.error(msg);
                 throw new CloudRuntimeException(msg);
@@ -2075,14 +2196,14 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
                 logger.error(msg);
                 throw new CloudRuntimeException(msg);
             }
-            if (getStoragePoolWithTags(pools, diskOffering.getTags()) == null) {
+            if (getStoragePoolWithTags(poolsList, diskOffering.getTags()) == null) {
                 String msg = String.format("Cannot find suitable storage pool for disk offering %s", diskOffering.getName());
                 logger.error(msg);
                 throw new CloudRuntimeException(msg);
             }
         }
 
-        return pools;
+        return poolsList;
     }
 
     private StoragePoolVO getStoragePoolWithTags(List<StoragePoolVO> pools, String tags) {
@@ -2128,39 +2249,63 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         throw new CloudRuntimeException(msg);
     }
 
+    private void checkBeforeSelectingTemporaryConversionStoragePool(StoragePoolVO selectedStoragePool, Long convertStoragePoolId, Cluster destinationCluster, HostVO convertHost) {
+        if (selectedStoragePool == null) {
+            logFailureAndThrowException(String.format("Cannot find a storage pool with ID %s", convertStoragePoolId));
+        }
+        if ((selectedStoragePool.getScope() == ScopeType.CLUSTER && selectedStoragePool.getClusterId() != destinationCluster.getId()) ||
+                (selectedStoragePool.getScope() == ScopeType.ZONE && selectedStoragePool.getDataCenterId() != destinationCluster.getDataCenterId())) {
+            logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
+                    "it is not in the scope of the cluster %s", selectedStoragePool.getName(), destinationCluster.getName()));
+        }
+        if (convertHost != null && selectedStoragePool.getScope() == ScopeType.CLUSTER && !selectedStoragePool.getClusterId().equals(convertHost.getClusterId())) {
+            logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
+                    "the host %s for conversion is in a different cluster", selectedStoragePool.getName(), convertHost.getName()));
+        }
+    }
+
+    private DataStoreTO getImageStoreOnDestinationZoneForTemporaryConversion(Cluster destinationCluster, boolean forceConvertToPool) {
+        if (forceConvertToPool) {
+            logFailureAndThrowException("Please select a primary storage pool when the parameter forceconverttopool is set to true");
+        }
+        long zoneId = destinationCluster.getDataCenterId();
+        ImageStoreVO imageStore = imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
+        if (imageStore == null) {
+            logFailureAndThrowException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " +
+                    "for instance conversion", zoneId));
+        }
+        DataStore dataStore = dataStoreManager.getDataStore(imageStore.getId(), DataStoreRole.Image);
+        return dataStore.getTO();
+    }
+
+    private void checkDestinationOrTemporaryStoragePoolForConversion(StoragePoolVO selectedStoragePool, boolean forceConvertToPool, HostVO convertHost, HostVO importHost) {
+        if (selectedStoragePool.getScope() == ScopeType.HOST && (ObjectUtils.anyNull(convertHost, importHost) ||
+                ObjectUtils.allNotNull(convertHost, importHost) && convertHost.getId() != importHost.getId() ||
+                !forceConvertToPool) ) {
+            logFailureAndThrowException("Please select the same host as convert and importing host and " +
+                    "set forceconvertopool to true to use a local storage pool for conversion");
+        }
+        if (!forceConvertToPool && selectedStoragePool.getPoolType() != Storage.StoragePoolType.NetworkFilesystem) {
+            logFailureAndThrowException(String.format("The storage pool %s is not supported for temporary conversion location," +
+                    "only NFS storage pools are supported when forceconverttopool is set to false", selectedStoragePool.getName()));
+        }
+    }
+
     protected DataStoreTO selectInstanceConversionTemporaryLocation(Cluster destinationCluster,
-                                                                    HostVO convertHost,
-                                                                    Long convertStoragePoolId) {
-        if (convertStoragePoolId != null) {
-            StoragePoolVO selectedStoragePool = primaryDataStoreDao.findById(convertStoragePoolId);
-            if (selectedStoragePool == null) {
-                logFailureAndThrowException(String.format("Cannot find a storage pool with ID %s", convertStoragePoolId));
-            }
-            if ((selectedStoragePool.getScope() == ScopeType.CLUSTER && selectedStoragePool.getClusterId() != destinationCluster.getId()) ||
-                    (selectedStoragePool.getScope() == ScopeType.ZONE && selectedStoragePool.getDataCenterId() != destinationCluster.getDataCenterId())) {
-                logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
-                        "it is not in the scope of the cluster %s", selectedStoragePool.getName(), destinationCluster.getName()));
-            }
-            if (convertHost != null && selectedStoragePool.getScope() == ScopeType.CLUSTER && !selectedStoragePool.getClusterId().equals(convertHost.getClusterId())) {
-                logFailureAndThrowException(String.format("Cannot use the storage pool %s for the instance conversion as " +
-                        "the host %s for conversion is in a different cluster", selectedStoragePool.getName(), convertHost.getName()));
-            }
-            if (selectedStoragePool.getScope() == ScopeType.HOST) {
-                logFailureAndThrowException(String.format("The storage pool %s is a local storage pool and not supported for temporary conversion location, cluster and zone wide NFS storage pools are supported", selectedStoragePool.getName()));
-            } else if (selectedStoragePool.getPoolType() != Storage.StoragePoolType.NetworkFilesystem) {
-                logFailureAndThrowException(String.format("The storage pool %s is not supported for temporary conversion location, only NFS storage pools are supported", selectedStoragePool.getName()));
-            }
-            return dataStoreManager.getPrimaryDataStore(convertStoragePoolId).getTO();
-        } else {
-            long zoneId = destinationCluster.getDataCenterId();
-            ImageStoreVO imageStore = imageStoreDao.findOneByZoneAndProtocol(zoneId, "nfs");
-            if (imageStore == null) {
-                logFailureAndThrowException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " +
-                        "for instance conversion", zoneId));
-            }
-            DataStore dataStore = dataStoreManager.getDataStore(imageStore.getId(), DataStoreRole.Image);
-            return dataStore.getTO();
+                                                                    HostVO convertHost, HostVO importHost,
+                                                                    Long convertStoragePoolId, boolean forceConvertToPool) {
+        if (convertStoragePoolId == null) {
+            String msg = String.format("No convert storage pool has been provided, " +
+                    "selecting an NFS secondary storage pool from the destination cluster (%s) zone", destinationCluster.getName());
+            logger.debug(msg);
+            return getImageStoreOnDestinationZoneForTemporaryConversion(destinationCluster, forceConvertToPool);
         }
+
+        StoragePoolVO selectedStoragePool = primaryDataStoreDao.findById(convertStoragePoolId);
+        checkBeforeSelectingTemporaryConversionStoragePool(selectedStoragePool, convertStoragePoolId, destinationCluster, convertHost);
+        checkDestinationOrTemporaryStoragePoolForConversion(selectedStoragePool, forceConvertToPool, convertHost, importHost);
+
+        return dataStoreManager.getPrimaryDataStore(convertStoragePoolId).getTO();
     }
 
     protected Map<String, String> createParamsForTemplateFromVmwareVmMigration(String vcenterHost, String datacenterName,
@@ -2186,6 +2331,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
         cmdList.add(UnmanageVMInstanceCmd.class);
         cmdList.add(ListVmsForImportCmd.class);
         cmdList.add(ImportVmCmd.class);
+        cmdList.add(ListImportVMTasksCmd.class);
         return cmdList;
     }
 
@@ -2865,7 +3011,9 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
                 RemoteKvmInstanceDisksCopyTimeout,
                 ConvertVmwareInstanceToKvmTimeout,
                 ThreadsOnMSToImportVMwareVMFiles,
-                ThreadsOnKVMHostToImportVMwareVMFiles
+                ThreadsOnKVMHostToImportVMwareVMFiles,
+                ConvertVmwareInstanceToKvmExtraParamsAllowed,
+                ConvertVmwareInstanceToKvmExtraParamsAllowedList
         };
     }
 }
diff --git a/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml b/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml
index 0215abf44b4..3afae7676b7 100644
--- a/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml
@@ -37,4 +37,6 @@
 
     <bean id="vmImportService" class="org.apache.cloudstack.vm.UnmanagedVMsManagerImpl" />
 
+    <bean id="importVmTasksManager" class="org.apache.cloudstack.vm.ImportVmTasksManagerImpl" />
+
 </beans>
diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
index 99e461949be..465cea3c74a 100644
--- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
+++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.UUID;
 
 import com.cloud.offering.DiskOffering;
+import com.cloud.vm.ImportVMTaskVO;
 import org.apache.cloudstack.api.ResponseGenerator;
 import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.ServerApiException;
@@ -54,6 +55,7 @@ import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationSe
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
@@ -236,6 +238,8 @@ public class UnmanagedVMsManagerImplTest {
     private DataStoreManager dataStoreManager;
     @Mock
     private StoragePoolHostDao storagePoolHostDao;
+    @Mock
+    private ImportVmTasksManager importVmTasksManager;
 
     @Mock
     private VMInstanceVO virtualMachine;
@@ -244,8 +248,15 @@ public class UnmanagedVMsManagerImplTest {
     @Mock
     DeploymentPlanningManager deploymentPlanningManager;
     @Mock
+    ImportVMTaskVO importVMTaskVO;
+    @Mock
     private VMInstanceDetailsDao vmInstanceDetailsDao;
 
+    @Mock
+    private ConfigKey<Boolean> configKeyMockParamsAllowed;
+    @Mock
+    private ConfigKey<String> configKeyMockParamsAllowedList;
+
     private static final long virtualMachineId = 1L;
 
     private AutoCloseable closeable;
@@ -720,7 +731,6 @@ public class UnmanagedVMsManagerImplTest {
         when(dataStore.getTO()).thenReturn(dataStoreTO);
 
         StoragePoolVO destPool = mock(StoragePoolVO.class);
-        when(destPool.getUuid()).thenReturn(UUID.randomUUID().toString());
         when(destPool.getDataCenterId()).thenReturn(zoneId);
         when(destPool.getClusterId()).thenReturn(null);
         when(destPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
@@ -772,6 +782,9 @@ public class UnmanagedVMsManagerImplTest {
             when(agentManager.send(Mockito.eq(convertHostId), Mockito.any(CheckConvertInstanceCommand.class))).thenReturn(checkConvertInstanceAnswer);
         }
 
+        when(importVMTaskVO.getId()).thenReturn(1L);
+        when(importVmTasksManager.createImportVMTaskRecord(any(DataCenter.class), any(Account.class), anyLong(), anyString(),
+                anyString(), anyString(), anyString(), any(Host.class), any(Host.class))).thenReturn(importVMTaskVO);
         when(volumeApiService.doesStoragePoolSupportDiskOffering(any(StoragePool.class), any(DiskOffering.class))).thenReturn(true);
 
         ConvertInstanceAnswer convertInstanceAnswer = mock(ConvertInstanceAnswer.class);
@@ -895,7 +908,7 @@ public class UnmanagedVMsManagerImplTest {
 
         long poolId = 1L;
         when(primaryDataStoreDao.findById(poolId)).thenReturn(null);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, poolId);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null, poolId, false);
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -906,7 +919,7 @@ public class UnmanagedVMsManagerImplTest {
         when(pool.getScope()).thenReturn(ScopeType.CLUSTER);
         when(pool.getClusterId()).thenReturn(100L);
         when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, poolId);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null, poolId, false);
     }
 
 
@@ -920,7 +933,7 @@ public class UnmanagedVMsManagerImplTest {
         HostVO host = mock(HostVO.class);
         when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
         when(host.getClusterId()).thenReturn(2L);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, host, poolId);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, host, null, poolId, false);
     }
 
 
@@ -931,7 +944,7 @@ public class UnmanagedVMsManagerImplTest {
         StoragePoolVO pool = mock(StoragePoolVO.class);
         when(pool.getScope()).thenReturn(ScopeType.HOST);
         when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, poolId);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null, poolId, false);
     }
 
     @Test(expected = CloudRuntimeException.class)
@@ -943,14 +956,14 @@ public class UnmanagedVMsManagerImplTest {
         when(pool.getClusterId()).thenReturn(1L);
         when(primaryDataStoreDao.findById(poolId)).thenReturn(pool);
         when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.RBD);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, poolId);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null, poolId, false);
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void testSelectInstanceConversionTemporaryLocationNoPoolAvailable() {
         ClusterVO cluster = getClusterForTests();
         when(imageStoreDao.findOneByZoneAndProtocol(anyLong(), anyString())).thenReturn(null);
-        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null);
+        unmanagedVMsManager.selectInstanceConversionTemporaryLocation(cluster, null, null, null, false);
     }
 
     @Test
@@ -1220,4 +1233,81 @@ public class UnmanagedVMsManagerImplTest {
             Assert.fail("Exception encountered: " + e.getMessage());
         }
     }
+
+    @Test
+    public void testCheckConversionStoragePoolSecondaryStorageStaging() {
+        unmanagedVMsManager.checkConversionStoragePool(null, false);
+        Mockito.verifyNoInteractions(primaryDataStoreDao);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testCheckConversionStoragePoolTemporarySecondaryStorageForceConvertToPool() {
+        unmanagedVMsManager.checkConversionStoragePool(null, true);
+    }
+
+    @Test
+    public void testCheckConversionStoragePoolPrimaryStagingPool() {
+        StoragePoolVO destPool = mock(StoragePoolVO.class);
+        long destPoolId = 1L;
+        Mockito.when(primaryDataStoreDao.findById(destPoolId)).thenReturn(destPool);
+        unmanagedVMsManager.checkConversionStoragePool(destPoolId, false);
+    }
+
+    @Test
+    public void testCheckConversionStoragePoolPrimaryStagingPoolTypeAllowedForce() {
+        StoragePoolVO destPool = mock(StoragePoolVO.class);
+        Mockito.when(destPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
+        long destPoolId = 1L;
+        Mockito.when(primaryDataStoreDao.findById(destPoolId)).thenReturn(destPool);
+        unmanagedVMsManager.checkConversionStoragePool(destPoolId, true);
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testCheckConversionStoragePoolPrimaryStagingPoolTypeNotAllowedForce() {
+        StoragePoolVO destPool = mock(StoragePoolVO.class);
+        Mockito.when(destPool.getPoolType()).thenReturn(Storage.StoragePoolType.RBD);
+        long destPoolId = 1L;
+        Mockito.when(primaryDataStoreDao.findById(destPoolId)).thenReturn(destPool);
+        unmanagedVMsManager.checkConversionStoragePool(destPoolId, true);
+    }
+
+    @Test
+    public void testCheckExtraParamsAllowedEmptyParams() {
+        unmanagedVMsManager.checkExtraParamsAllowed(null);
+        Mockito.verifyNoInteractions(configKeyMockParamsAllowed);
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testCheckExtraParamsAllowedDisabledByAdministrator() {
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowed = configKeyMockParamsAllowed;
+        Mockito.when(configKeyMockParamsAllowed.value()).thenReturn(false);
+        unmanagedVMsManager.checkExtraParamsAllowed("--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x");
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testCheckExtraParamsAllowedEnabledButEmptyAllowedList() {
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowed = configKeyMockParamsAllowed;
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowedList = configKeyMockParamsAllowedList;
+        Mockito.when(configKeyMockParamsAllowed.value()).thenReturn(true);
+        Mockito.when(configKeyMockParamsAllowedList.value()).thenReturn(null);
+        unmanagedVMsManager.checkExtraParamsAllowed("--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x");
+    }
+
+    @Test
+    public void testCheckExtraParamsAllowedEnabledAndAllowedList() {
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowed = configKeyMockParamsAllowed;
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowedList = configKeyMockParamsAllowedList;
+        Mockito.when(configKeyMockParamsAllowed.value()).thenReturn(true);
+        Mockito.when(configKeyMockParamsAllowedList.value()).thenReturn("mac,network,x");
+        unmanagedVMsManager.checkExtraParamsAllowed("--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x");
+    }
+
+    @Test(expected = ServerApiException.class)
+    public void testCheckExtraParamsAllowedEnabledParamNotInTheAllowedList() {
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowed = configKeyMockParamsAllowed;
+        unmanagedVMsManager.ConvertVmwareInstanceToKvmExtraParamsAllowedList = configKeyMockParamsAllowedList;
+        Mockito.when(configKeyMockParamsAllowed.value()).thenReturn(true);
+        Mockito.when(configKeyMockParamsAllowedList.value()).thenReturn("network,x");
+        unmanagedVMsManager.checkExtraParamsAllowed("--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x");
+    }
 }
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index ad72b4c7938..e41a04ff2e1 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -272,7 +272,8 @@ known_categories = {
     'Extension' : 'Extension',
     'Extensions' : 'Extension',
     'CustomAction' : 'Extension',
-    'CustomActions' : 'Extension'
+    'CustomActions' : 'Extension',
+    'ImportVmTask': 'Import VM Task'
 }
 
 
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 3d00580dceb..a877b9f7c0d 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1089,6 +1089,7 @@
 "label.forcks": "For CKS",
 "label.forbidden": "Forbidden",
 "label.forced": "Force",
+"label.force.convert.to.pool": "Force converting to storage pool directly (not using temporary storage for conversion)",
 "label.force.ms.to.import.vm.files": "Enable to force OVF Download via Management Server. Disable to use KVM Host ovftool (if installed)",
 "label.force.update.os.type": "Force update OS type",
 "label.force.stop": "Force stop",
@@ -3732,8 +3733,10 @@
 "message.select.deselect.desired.options": "Please select / deselect the desired options",
 "message.select.deselect.to.sort": "Please select / deselect to sort the values",
 "message.select.destination.image.stores": "Please select Image Store(s) to which data is to be migrated to",
+"message.select.destination.storage.instance.conversion": "(Optional) Select a Primary Storage destination for the converted disks",
 "message.select.disk.offering": "Please select a disk offering for disk",
 "message.select.end.date.and.time": "Select an end date & time.",
+"message.select.extra.parameters.for.instance.conversion": "(Optional) Pass extra parameters to the virt-v2v command on the conversion host",
 "message.select.kvm.host.instance.conversion": "(Optional) Select a KVM host in the Zone to perform the instance conversion through virt-v2v",
 "message.select.kvm.host.instance.import": "(Optional) Select a KVM host in the Cluster to perform the importing of the converted instance",
 "message.select.load.balancer.rule": "Please select a load balancer rule for your AutoScale Instance group.",
@@ -3741,7 +3744,7 @@
 "message.select.nic.network": "Please select a Network for NIC",
 "message.select.security.groups": "Please select security group(s) for your new Instance.",
 "message.select.start.date.and.time": "Select a start date & time.",
-"message.select.temporary.storage.instance.conversion": "(Optional) Select a Storage temporary destination for the converted disks through virt-v2v",
+"message.select.temporary.storage.instance.conversion": "(Optional) Select a staging storage for the converted disks",
 "message.select.volume.to.continue": "Please select a volume to continue.",
 "message.select.vm.to.continue": "Please select an Instance to continue.",
 "message.select.zone.description": "Select type of Zone basic/advanced.",
diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue
index 4c1a3d53fbc..8cc27b2a86d 100644
--- a/ui/src/views/tools/ImportUnmanagedInstance.vue
+++ b/ui/src/views/tools/ImportUnmanagedInstance.vue
@@ -152,6 +152,12 @@
                   </a-row>
                 </a-radio-group>
               </a-form-item>
+              <a-form-item name="forceconverttopool" ref="forceconverttopool" v-if="selectedVmwareVcenter">
+                <template #label>
+                  <tooltip-label :title="$t('label.force.convert.to.pool')" :tooltip="apiParams.forceconverttopool.description"/>
+                </template>
+                <a-switch v-model:checked="form.forceconverttopool" @change="onForceConvertToPoolChange" />
+              </a-form-item>
               <a-form-item name="converthostid" ref="converthostid">
                 <check-box-select-pair
                   layout="vertical"
@@ -179,17 +185,21 @@
               <a-form-item name="convertstorageoption" ref="convertstorageoption">
                 <check-box-select-pair
                   layout="vertical"
-                  style="margin-bottom: 5px"
                   v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter"
                   :resourceKey="cluster.id"
                   :selectOptions="storageOptionsForConversion"
-                  :checkBoxLabel="$t('message.select.temporary.storage.instance.conversion')"
+                  :checkBoxLabel="switches.forceConvertToPool ? $t('message.select.destination.storage.instance.conversion') : $t('message.select.temporary.storage.instance.conversion')"
                   :defaultCheckBoxValue="false"
                   :reversed="false"
                   @handle-checkselectpair-change="updateSelectedStorageOptionForConversion"
                 />
               </a-form-item>
-              <a-form-item v-if="showStoragePoolsForConversion" name="convertstoragepool" ref="convertstoragepool" :label="$t('label.storagepool')">
+              <a-form-item
+                v-if="showStoragePoolsForConversion"
+                name="convertstoragepool"
+                ref="convertstoragepool"
+                :label="$t('label.storagepool')"
+              >
                 <a-select
                   v-model:value="form.convertstoragepoolid"
                   defaultActiveFirstOption
@@ -204,6 +214,18 @@
                   </a-select-option>
                 </a-select>
               </a-form-item>
+              <a-form-item name="extraparams" ref="extraparams">
+                <a-checkbox
+                  v-if="cluster.hypervisortype === 'KVM' && selectedVmwareVcenter && vmwareToKvmExtraParamsAllowed"
+                  v-model:checked="vmwareToKvmExtraParamsSelected">
+                  {{ $t('message.select.extra.parameters.for.instance.conversion') }}
+                </a-checkbox>
+                <a-input
+                  v-if="vmwareToKvmExtraParamsSelected"
+                  v-model:value="vmwareToKvmExtraParams"
+                  :placeholder="$t('label.extra')"
+                />
+              </a-form-item>
               <a-form-item name="forcemstoimportvmfiles" ref="forcemstoimportvmfiles" v-if="selectedVmwareVcenter">
                 <template #label>
                   <tooltip-label :title="$t('label.force.ms.to.import.vm.files')" :tooltip="apiParams.forcemstoimportvmfiles.description"/>
@@ -529,7 +551,10 @@ export default {
           title: this.$t('label.rootdisk')
         }
       ],
-      selectedRootDiskSources: []
+      selectedRootDiskSources: [],
+      vmwareToKvmExtraParamsAllowed: false,
+      vmwareToKvmExtraParamsSelected: false,
+      vmwareToKvmExtraParams: ''
     }
   },
   beforeCreate () {
@@ -724,6 +749,7 @@ export default {
         migrateallowed: this.switches.migrateAllowed,
         forced: this.switches.forced,
         forcemstoimportvmfiles: this.switches.forceMsToImportVmFiles,
+        forceconverttopool: this.switches.forceConvertToPool,
         domainid: null,
         account: null
       })
@@ -749,6 +775,20 @@ export default {
       if (this.resource?.disk?.length > 1) {
         this.updateSelectedRootDisk()
       }
+      this.fetchVmwareToKVMExtraConfigsSetting()
+    },
+    fetchVmwareToKVMExtraConfigsSetting () {
+      const params = {
+        name: 'convert.vmware.instance.to.kvm.extra.params.allowed'
+      }
+      getAPI('listConfigurations', params).then(json => {
+        if (json.listconfigurationsresponse.configuration !== null) {
+          const config = json.listconfigurationsresponse.configuration[0]
+          if (config && config.name === params.name) {
+            this.vmwareToKvmExtraParamsAllowed = config.value
+          }
+        }
+      })
     },
     getMeta (obj, metaKeys) {
       var meta = []
@@ -1027,20 +1067,23 @@ export default {
       }
     },
     resetStorageOptionsForConversion () {
-      this.storageOptionsForConversion = [
-        {
-          id: 'secondary',
-          name: 'Secondary Storage'
-        }, {
-          id: 'primary',
-          name: 'Primary Storage'
-        }
-      ]
+      this.storageOptionsForConversion = this.switches.forceConvertToPool ? [] : [{
+        id: 'secondary',
+        name: 'Secondary Storage'
+      }]
+      this.storageOptionsForConversion.push({
+        id: 'primary',
+        name: 'Primary Storage'
+      })
     },
     onSelectRootDisk (val) {
       this.selectedRootDiskIndex = val
       this.updateSelectedRootDisk()
     },
+    onForceConvertToPoolChange (val) {
+      this.switches.forceConvertToPool = val
+      this.resetStorageOptionsForConversion()
+    },
     updateSelectedRootDisk () {
       var rootDisk = this.resource.disk[this.selectedRootDiskIndex]
       rootDisk.size = rootDisk.capacity / (1024 * 1024 * 1024)
@@ -1156,7 +1199,13 @@ export default {
           if (this.selectedStoragePoolForConversion) {
             params.convertinstancepoolid = this.selectedStoragePoolForConversion
           }
+          if (this.vmwareToKvmExtraParams) {
+            params.extraparams = this.vmwareToKvmExtraParams
+          }
           params.forcemstoimportvmfiles = values.forcemstoimportvmfiles
+          if (values.forceconverttopool) {
+            params.forceconverttopool = values.forceconverttopool
+          }
         }
         var keys = ['hostname', 'domainid', 'projectid', 'account', 'migrateallowed', 'forced', 'forcemstoimportvmfiles']
         if (this.templateType !== 'auto') {
diff --git a/ui/src/views/tools/ImportVmTasks.vue b/ui/src/views/tools/ImportVmTasks.vue
new file mode 100644
index 00000000000..51741f18e69
--- /dev/null
+++ b/ui/src/views/tools/ImportVmTasks.vue
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-card class="instances-card">
+    <template #title>
+      Import VM Tasks
+      <a-tooltip :title="'Running Import VM Tasks'">
+        <info-circle-outlined />
+      </a-tooltip>
+      <a-button
+        style="margin-left: 12px; margin-top: 4px"
+        :loading="loading"
+        size="small"
+        shape="round"
+        @click="fetchData()">
+        <template #icon><reload-outlined /></template>
+      </a-button>
+      <a-select
+        :placeholder="$t('label.filterby')"
+        :value="importVmTasksFilterValue"
+        style="min-width: 100px; margin-left: 10px; margin-bottom: 5px"
+        size=small
+        @change="onFilterChange"
+        showSearch
+        optionFilterProp="label"
+        :filterOption="(input, option) => {
+          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+        }"
+      >
+        <template #suffixIcon><filter-outlined class="ant-select-suffix" /></template>
+          <a-select-option
+            v-for="filter in filters"
+            :key="filter"
+            :label="$t('label.' + filter)"
+          >
+            {{ $t('label.' + filter) }}
+          </a-select-option>
+        </a-select>
+    </template>
+    <a-table
+      :data-source="tasks"
+      :columns="columns">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'convertinstancehostid'">
+            <router-link :to="{ path: '/host/' + record.convertinstancehostid }">{{ record.convertinstancehostname }}</router-link>
+          </template>
+          <template v-else-if="column.key === 'displayname'">
+            <router-link v-if="record.virtualmachineid" :to="{ path: '/vm/' + record.virtualmachineid }">{{ record.displayname }}</router-link>
+            <span v-else>{{ record.displayname }}</span>
+          </template>
+        </template>
+    </a-table>
+  </a-card>
+</template>
+
+<script>
+export default {
+  name: 'ImportVmTasks',
+  components: {
+  },
+  props: {
+    tasks: {
+      type: Array,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      required: false
+    }
+  },
+  data () {
+    const columns = [
+      {
+        key: 'displayname',
+        title: 'VM Display Name',
+        dataIndex: 'displayname'
+      },
+      {
+        key: 'convertinstancehostid',
+        title: 'Conversion Host',
+        dataIndex: 'convertinstancehostid'
+      },
+      {
+        key: 'step',
+        title: 'Current Step',
+        dataIndex: 'step'
+      },
+      {
+        key: 'stepduration',
+        title: 'Current Step Duration',
+        dataIndex: 'stepduration'
+      },
+      {
+        key: 'description',
+        title: 'Description',
+        dataIndex: 'description'
+      },
+      {
+        key: 'duration',
+        title: 'Total Duration',
+        dataIndex: 'duration'
+      },
+      {
+        key: 'sourcevmname',
+        title: 'Source VM Name',
+        dataIndex: 'sourcevmname'
+      },
+      {
+        key: 'vcenter',
+        title: 'vCenter',
+        dataIndex: 'vcenter'
+      },
+      {
+        key: 'datacentername',
+        title: 'Datacenter Name',
+        dataIndex: 'datacentername'
+      }
+    ]
+    return {
+      columns,
+      filters: ['running', 'completed'],
+      filterValue: 'running'
+    }
+  },
+  methods: {
+    fetchData () {
+      this.$emit('fetch-import-vm-tasks', this.filterValue)
+    },
+    onFilterChange (e) {
+      this.filterValue = e
+      this.fetchData()
+    }
+  }
+}
+</script>
diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue
index b6f3c50e267..d58716df3aa 100644
--- a/ui/src/views/tools/ManageInstances.vue
+++ b/ui/src/views/tools/ManageInstances.vue
@@ -333,166 +333,179 @@
             </a-col>
           </a-row>
           <a-divider />
-          <a-row :gutter="12">
-            <a-col v-if="!isDiskImport" :md="24" :lg="(!isMigrateFromVmware && showManagedInstances) ? 12 : 24">
-              <a-card class="instances-card">
-                <template #title>
-                  {{ (isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('label.instances') : $t('label.unmanaged.instances') }}
-                  <a-tooltip :title="(isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('message.instances.migrate.vmware') : $t('message.instances.unmanaged')">
-                    <info-circle-outlined />
-                  </a-tooltip>
-                  <a-button
-                    style="margin-left: 12px; margin-top: 4px"
-                    :loading="unmanagedInstancesLoading"
-                    size="small"
-                    shape="round"
-                    @click="fetchUnmanagedInstances()" >
-                    <template #icon><reload-outlined /></template>
-                  </a-button>
-                  <span style="float: right; width: 50%">
-                    <search-view
-                      :searchFilters="searchFilters.unmanaged"
-                      :searchParams="searchParams.unmanaged"
-                      :apiName="listInstancesApi.unmanaged"
-                      @search="searchUnmanagedInstances"
-                    />
-                  </span>
-                </template>
-                <a-table
-                  v-if="!isExternal"
-                  class="instances-card-table"
-                  :loading="unmanagedInstancesLoading"
-                  :rowSelection="unmanagedInstanceSelection"
-                  :rowKey="(record, index) => index"
-                  :columns="unmanagedInstancesColumns"
-                  :data-source="unmanagedInstances"
-                  :pagination="false"
-                  size="middle"
-                  :rowClassName="getRowClassName"
-                >
-                  <template #bodyCell="{ column, text }">
-                    <template v-if="column.key === 'state'">
-                      <status :text="text ? text : ''" displayText />
+
+          <a-tabs v-model:activeKey="activeTabKey" @change="onTabChange">
+            <a-tab-pane :key=1 tab="Instances Listing">
+              <a-row :gutter="12">
+                <a-col v-if="!isDiskImport" :md="24" :lg="(!isMigrateFromVmware && showManagedInstances) ? 12 : 24">
+                  <a-card class="instances-card">
+                    <template #title>
+                      {{ (isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('label.instances') : $t('label.unmanaged.instances') }}
+                      <a-tooltip :title="(isMigrateFromVmware && vmwareVcenterType === 'existing') ? $t('message.instances.migrate.vmware') : $t('message.instances.unmanaged')">
+                        <info-circle-outlined />
+                      </a-tooltip>
+                      <a-button
+                        style="margin-left: 12px; margin-top: 4px"
+                        :loading="unmanagedInstancesLoading"
+                        size="small"
+                        shape="round"
+                        @click="fetchUnmanagedInstances()" >
+                        <template #icon><reload-outlined /></template>
+                      </a-button>
+                      <span style="float: right; width: 50%">
+                        <search-view
+                          :searchFilters="searchFilters.unmanaged"
+                          :searchParams="searchParams.unmanaged"
+                          :apiName="listInstancesApi.unmanaged"
+                          @search="searchUnmanagedInstances"
+                        />
+                      </span>
                     </template>
-                  </template>
-                </a-table>
-                <a-table
-                  v-if="isExternal"
-                  class="instances-card-table"
-                  :loading="unmanagedInstancesLoading"
-                  :rowSelection="unmanagedInstanceSelection"
-                  :rowKey="(record, index) => index"
-                  :columns="externalInstancesColumns"
-                  :data-source="unmanagedInstances"
-                  :pagination="false"
-                  size="middle"
-                  :rowClassName="getRowClassName"
-                >
-                  <template #bodyCell="{ column, text }">
-                      <template v-if="column.key === 'state'">
+                    <a-table
+                      v-if="!isExternal"
+                      class="instances-card-table"
+                      :loading="unmanagedInstancesLoading"
+                      :rowSelection="unmanagedInstanceSelection"
+                      :rowKey="(record, index) => index"
+                      :columns="unmanagedInstancesColumns"
+                      :data-source="unmanagedInstances"
+                      :pagination="false"
+                      size="middle"
+                      :rowClassName="getRowClassName"
+                    >
+                      <template #bodyCell="{ column, text }">
+                        <template v-if="column.key === 'state'">
                           <status :text="text ? text : ''" displayText />
+                        </template>
                       </template>
-                  </template>
-                </a-table>
-                <div class="instances-card-footer">
-                  <a-pagination
-                    class="row-element"
-                    size="small"
-                    :current="page.unmanaged"
-                    :pageSize="pageSize.unmanaged"
-                    :total="itemCount.unmanaged"
-                    :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.unmanaged-1)*pageSize.unmanaged))}-${Math.min(page.unmanaged*pageSize.unmanaged, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
-                    @change="fetchUnmanagedInstances"
-                    showQuickJumper>
-                    <template #buildOptionText="props">
-                      <span>{{ props.value }} / {{ $t('label.page') }}</span>
-                    </template>
-                  </a-pagination>
-                  <div :span="24" class="action-button-right">
-                    <a-button
-                      :loading="importUnmanagedInstanceLoading"
-                      :disabled="!(('importUnmanagedInstance' in $store.getters.apis) && unmanagedInstancesSelectedRowKeys.length > 0)"
-                      type="primary"
-                      @click="onManageInstanceAction">
-                      <template #icon><import-outlined /></template>
-                      {{ $t('label.import.instance') }}
-                    </a-button>
-                  </div>
-                </div>
-              </a-card>
-            </a-col>
-            <a-col :md="24" :lg="12" v-if="!isMigrateFromVmware && showManagedInstances">
-              <a-card class="instances-card">
-                <template #title>
-                  {{ $t('label.managed.instances') }}
-                  <a-tooltip :title="$t('message.instances.managed')">
-                    <info-circle-outlined />
-                  </a-tooltip>
-                  <a-button
-                    style="margin-left: 12px; margin-top: 4px"
-                    :loading="managedInstancesLoading"
-                    size="small"
-                    shape="round"
-                    @click="fetchManagedInstances()" >
-                    <template #icon><reload-outlined /></template>
-                  </a-button>
-                  <span style="float: right; width: 50%">
-                    <search-view
-                      :searchFilters="searchFilters.managed"
-                      :searchParams="searchParams.managed"
-                      :apiName="listInstancesApi.managed"
-                      @search="searchManagedInstances"
-                    />
-                  </span>
-                </template>
-                <a-table
-                  class="instances-card-table"
-                  :loading="managedInstancesLoading"
-                  :rowSelection="managedInstanceSelection"
-                  :rowKey="(record, index) => index"
-                  :columns="managedInstancesColumns"
-                  :data-source="managedInstances"
-                  :pagination="false"
-                  size="middle"
-                  :rowClassName="getRowClassName"
-                >
-                  <template #bodyCell="{ column, text, record }">
-                    <template v-if="column.key === 'name'">
-                      <router-link :to="{ path: '/vm/' + record.id }">{{ text }}</router-link>
-                    </template>
-                    <template v-if="column.key === 'state'">
-                      <status :text="text ? text : ''" displayText />
-                    </template>
-                  </template>
-                </a-table>
-                <div class="instances-card-footer">
-                  <a-pagination
-                    class="row-element"
-                    size="small"
-                    :current="page.managed"
-                    :pageSize="pageSize.managed"
-                    :total="itemCount.managed"
-                    :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.managed-1)*pageSize.managed))}-${Math.min(page.managed*pageSize.managed, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
-                    @change="fetchManagedInstances"
-                    showQuickJumper>
-                    <template #buildOptionText="props">
-                      <span>{{ props.value }} / {{ $t('label.page') }}</span>
+                    </a-table>
+                    <a-table
+                      v-if="isExternal"
+                      class="instances-card-table"
+                      :loading="unmanagedInstancesLoading"
+                      :rowSelection="unmanagedInstanceSelection"
+                      :rowKey="(record, index) => index"
+                      :columns="externalInstancesColumns"
+                      :data-source="unmanagedInstances"
+                      :pagination="false"
+                      size="middle"
+                      :rowClassName="getRowClassName"
+                    >
+                      <template #bodyCell="{ column, text }">
+                          <template v-if="column.key === 'state'">
+                              <status :text="text ? text : ''" displayText />
+                          </template>
+                      </template>
+                    </a-table>
+                    <div class="instances-card-footer">
+                      <a-pagination
+                        class="row-element"
+                        size="small"
+                        :current="page.unmanaged"
+                        :pageSize="pageSize.unmanaged"
+                        :total="itemCount.unmanaged"
+                        :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.unmanaged-1)*pageSize.unmanaged))}-${Math.min(page.unmanaged*pageSize.unmanaged, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
+                        @change="fetchUnmanagedInstances"
+                        showQuickJumper>
+                        <template #buildOptionText="props">
+                          <span>{{ props.value }} / {{ $t('label.page') }}</span>
+                        </template>
+                      </a-pagination>
+                      <div :span="24" class="action-button-right">
+                        <a-button
+                          :loading="importUnmanagedInstanceLoading"
+                          :disabled="!(('importUnmanagedInstance' in $store.getters.apis) && unmanagedInstancesSelectedRowKeys.length > 0)"
+                          type="primary"
+                          @click="onManageInstanceAction">
+                          <template #icon><import-outlined /></template>
+                          {{ $t('label.import.instance') }}
+                        </a-button>
+                      </div>
+                    </div>
+                  </a-card>
+                </a-col>
+                <a-col :md="24" :lg="12" v-if="!isMigrateFromVmware && showManagedInstances">
+                  <a-card class="instances-card">
+                    <template #title>
+                      {{ $t('label.managed.instances') }}
+                      <a-tooltip :title="$t('message.instances.managed')">
+                        <info-circle-outlined />
+                      </a-tooltip>
+                      <a-button
+                        style="margin-left: 12px; margin-top: 4px"
+                        :loading="managedInstancesLoading"
+                        size="small"
+                        shape="round"
+                        @click="fetchManagedInstances()" >
+                        <template #icon><reload-outlined /></template>
+                      </a-button>
+                      <span style="float: right; width: 50%">
+                        <search-view
+                          :searchFilters="searchFilters.managed"
+                          :searchParams="searchParams.managed"
+                          :apiName="listInstancesApi.managed"
+                          @search="searchManagedInstances"
+                        />
+                      </span>
                     </template>
-                  </a-pagination>
-                  <div :span="24" class="action-button-right">
-                    <a-button
+                    <a-table
+                      class="instances-card-table"
+                      :loading="managedInstancesLoading"
+                      :rowSelection="managedInstanceSelection"
+                      :rowKey="(record, index) => index"
+                      :columns="managedInstancesColumns"
+                      :data-source="managedInstances"
+                      :pagination="false"
+                      size="middle"
+                      :rowClassName="getRowClassName"
+                    >
+                      <template #bodyCell="{ column, text, record }">
+                        <template v-if="column.key === 'name'">
+                          <router-link :to="{ path: '/vm/' + record.id }">{{ text }}</router-link>
+                        </template>
+                        <template v-if="column.key === 'state'">
+                          <status :text="text ? text : ''" displayText />
+                        </template>
+                      </template>
+                    </a-table>
+                    <div class="instances-card-footer">
+                      <a-pagination
+                        class="row-element"
+                        size="small"
+                        :current="page.managed"
+                        :pageSize="pageSize.managed"
+                        :total="itemCount.managed"
+                        :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.managed-1)*pageSize.managed))}-${Math.min(page.managed*pageSize.managed, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
+                        @change="fetchManagedInstances"
+                        showQuickJumper>
+                        <template #buildOptionText="props">
+                          <span>{{ props.value }} / {{ $t('label.page') }}</span>
+                        </template>
+                      </a-pagination>
+                      <div :span="24" class="action-button-right">
+                        <a-button
+
+                          :disabled="!(('unmanageVirtualMachine' in $store.getters.apis) && managedInstancesSelectedRowKeys.length > 0)"
+                          type="primary"
+                          @click="onUnmanageInstanceAction">
+                          <template #icon><disconnect-outlined /></template>
+                          {{ managedInstancesSelectedRowKeys.length > 1 ? $t('label.action.unmanage.instances') : $t('label.action.unmanage.instance') }}
+                        </a-button>
+                      </div>
+                    </div>
+                  </a-card>
+                </a-col>
+              </a-row>
+            </a-tab-pane>
+            <a-tab-pane :key=2 tab="Import VM Tasks" v-if="isMigrateFromVmware">
+              <ImportVmTasks
+                :tasks="importVmTasks"
+                :loading="loadingImportVmTasks"
+                @fetch-import-vm-tasks="fetchImportVmTasks"
+              />
+            </a-tab-pane>
+          </a-tabs>
 
-                      :disabled="!(('unmanageVirtualMachine' in $store.getters.apis) && managedInstancesSelectedRowKeys.length > 0)"
-                      type="primary"
-                      @click="onUnmanageInstanceAction">
-                      <template #icon><disconnect-outlined /></template>
-                      {{ managedInstancesSelectedRowKeys.length > 1 ? $t('label.action.unmanage.instances') : $t('label.action.unmanage.instance') }}
-                    </a-button>
-                  </div>
-                </div>
-              </a-card>
-            </a-col>
-          </a-row>
         </a-card>
 
         <a-modal
@@ -546,6 +559,7 @@ import ImportUnmanagedInstances from '@/views/tools/ImportUnmanagedInstance'
 import ResourceIcon from '@/components/view/ResourceIcon'
 import SelectVmwareVcenter from '@/views/tools/SelectVmwareVcenter'
 import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
+import ImportVmTasks from '@/views/tools/ImportVmTasks.vue'
 
 export default {
   components: {
@@ -555,7 +569,8 @@ export default {
     SearchView,
     ImportUnmanagedInstances,
     ResourceIcon,
-    SelectVmwareVcenter
+    SelectVmwareVcenter,
+    ImportVmTasks
   },
   name: 'ManageVms',
   data () {
@@ -747,7 +762,10 @@ export default {
       selectedUnmanagedInstance: {},
       query: {},
       vmwareVcenterType: undefined,
-      selectedVmwareVcenter: undefined
+      selectedVmwareVcenter: undefined,
+      activeTabKey: 1,
+      loadingImportVmTasks: false,
+      importVmTasks: []
     }
   },
   created () {
@@ -1084,6 +1102,7 @@ export default {
       this.page.managed = 1
       this.managedInstances = []
       this.managedInstancesSelectedRowKeys = []
+      this.activeTabKey = 1
     },
     onSelectHypervisor (value) {
       this.sourceHypervisor = value
@@ -1154,6 +1173,27 @@ export default {
       this.updateQuery('scope', value)
       this.fetchOptions(this.params.pools, 'pools', value)
     },
+    onTabChange (e) {
+      if (e === 2) {
+        this.fetchImportVmTasks()
+      }
+    },
+    fetchImportVmTasks (filter) {
+      this.loadingImportVmTasks = true
+      const params = {
+        zoneid: this.zoneId
+      }
+      if (filter && filter === 'completed') {
+        params.showcompleted = true
+      }
+      getAPI('listImportVmTasks', params).then(response => {
+        this.importVmTasks = response.listimportvmtasksresponse.importvmtask || []
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.loadingImportVmTasks = false
+      })
+    },
     fetchInstances () {
       this.fetchUnmanagedInstances()
       if (this.isUnmanaged) {
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java
index 28ae3264355..b56a457aeaa 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -244,6 +244,7 @@ public class Script implements Callable<String> {
 
         try {
             _logger.trace(String.format("Creating process for command [%s].", commandLine));
+
             ProcessBuilder pb = new ProcessBuilder(command);
             pb.redirectErrorStream(true);
             if (_workDir != null)