mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-16 10:32:34 +01:00
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 <175728472+Copilot@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 <abhishek.mrt22@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
This commit is contained in:
parent
b99a03092f
commit
b106d6e190
@ -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=
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
28
api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java
Normal file
28
api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
259
engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java
Normal file
259
engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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();
|
||||
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, 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,4 +37,6 @@
|
||||
|
||||
<bean id="vmImportService" class="org.apache.cloudstack.vm.UnmanagedVMsManagerImpl" />
|
||||
|
||||
<bean id="importVmTasksManager" class="org.apache.cloudstack.vm.ImportVmTasksManagerImpl" />
|
||||
|
||||
</beans>
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +272,8 @@ known_categories = {
|
||||
'Extension' : 'Extension',
|
||||
'Extensions' : 'Extension',
|
||||
'CustomAction' : 'Extension',
|
||||
'CustomActions' : 'Extension'
|
||||
'CustomActions' : 'Extension',
|
||||
'ImportVmTask': 'Import VM Task'
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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') {
|
||||
|
||||
150
ui/src/views/tools/ImportVmTasks.vue
Normal file
150
ui/src/views/tools/ImportVmTasks.vue
Normal file
@ -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>
|
||||
@ -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 />
|
||||
</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'">
|
||||
<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-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-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>
|
||||
<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>
|
||||
</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-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>
|
||||
|
||||
</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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user