package org.ovirt.engine.core.bll.exportimport; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.ovirt.engine.core.bll.DisableInPrepareMode; import org.ovirt.engine.core.bll.LockMessage; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.VmCommand; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.storage.ovfstore.OvfHelper; import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil; import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.ConvertVmParameters; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.RemoveVmParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.OriginType; import org.ovirt.engine.core.common.businessentities.V2VJobInfo.JobStatus; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.vdscommands.ConvertVmVDSParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.VdsAndVmIDVDSParametersBase; import org.ovirt.engine.core.compat.CommandStatus; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.backendcompat.CommandExecutionStatus; import org.ovirt.engine.core.dao.DiskVmElementDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.utils.ovf.OvfReaderException; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.ovirt.engine.core.vdsbroker.VdsManager; import org.ovirt.engine.core.vdsbroker.VmManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @DisableInPrepareMode @NonTransactiveCommandAttribute public class ConvertVmCommand<T extends ConvertVmParameters> extends VmCommand<T> { private static final Logger log = LoggerFactory.getLogger(ConvertVmCommand.class); @Inject private ResourceManager resourceManager; @Inject private OvfHelper ovfHelper; @Inject private VdsDao vdsDao; @Inject private DiskVmElementDao diskVmElementDao; private ConvertVmCallback cachedCallback; public ConvertVmCommand(Guid commandId) { super(commandId); } public ConvertVmCommand(T parameters, CommandContext context) { super(parameters, context); } @Override protected void init() { super.init(); setVmName(getParameters().getVmName()); setVdsId(getParameters().getProxyHostId()); setClusterId(getParameters().getClusterId()); setStoragePoolId(getParameters().getStoragePoolId()); setStorageDomainId(getParameters().getStorageDomainId()); } @Override public CommandCallback getCallback() { if (cachedCallback == null) { cachedCallback = new ConvertVmCallback(getCommandId()); // if the callback is created after the command was executed, it means that the engine restarted // so there is no v2v-job in vdsManager and thus we add a new job with unknown status there if (getCommandExecutionStatus() == CommandExecutionStatus.EXECUTED) { monitorV2VJob(JobStatus.UNKNOWN); } } return cachedCallback; } private void monitorV2VJob(JobStatus initialJobStatus) { getVdsManager().addV2VJobInfoForVm(getVmId(), initialJobStatus); getVmManager().setConvertProxyHostId(getVdsId()); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Command); } @Override protected Map<String, Pair<String, String>> getExclusiveLocks() { return Collections.singletonMap(getVmId().toString(), LockMessagesMatchUtil.makeLockingPair( LockingGroup.VM, new LockMessage(EngineMessage.ACTION_TYPE_FAILED_VM_IS_BEING_IMPORTED) .with("VmName", getVmName()))); } @Override public AuditLogType getAuditLogTypeValue() { switch (getActionState()) { case EXECUTE: return getSucceeded()? AuditLogType.IMPORTEXPORT_STARTING_CONVERT_VM : AuditLogType.IMPORTEXPORT_IMPORT_VM_FAILED; case END_SUCCESS: return getSucceeded()? AuditLogType.IMPORTEXPORT_IMPORT_VM : AuditLogType.IMPORTEXPORT_IMPORT_VM_FAILED; case END_FAILURE: return AuditLogType.IMPORTEXPORT_IMPORT_VM_FAILED; } return super.getAuditLogTypeValue(); } /////////////////// //// Sync Part //// /////////////////// @Override protected boolean validate() { if (getVds() != null && getVds().getStatus() != VDSStatus.Up) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_VDS_STATUS_ILLEGAL); } if (getVds() == null && !selectProxyHost()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_NO_VDS_IN_POOL); } return true; } private boolean selectProxyHost() { List<VDS> activeHosts = vdsDao.getAllForStoragePoolAndStatus(getStoragePoolId(), VDSStatus.Up); if (activeHosts.isEmpty()) { return false; } VDS activeHost = activeHosts.get(0); setVds(activeHost); // update the parameters for the end-action phase getParameters().setProxyHostId(activeHost.getId()); return true; } @Override protected void executeVmCommand() { try { VDSReturnValue retValue = runVdsCommand(); if (retValue.getSucceeded()) { monitorV2VJob(JobStatus.WAIT_FOR_START); setSucceeded(true); } else { log.error("Failed to convert VM"); setCommandStatus(CommandStatus.FAILED); } } catch (EngineException e) { log.error("Failed to convert VM", e); setCommandStatus(CommandStatus.FAILED); } } protected VDSReturnValue runVdsCommand() { return runVdsCommand( VDSCommandType.ConvertVm, buildConvertParameters()); } private ConvertVmVDSParameters buildConvertParameters() { ConvertVmVDSParameters parameters = new ConvertVmVDSParameters(getVdsId()); parameters.setUrl(getParameters().getUrl()); parameters.setUsername(getParameters().getUsername()); parameters.setPassword(getParameters().getPassword()); parameters.setDisks(getParameters().getDisks()); parameters.setVmId(getVmId()); parameters.setVmName(getVmName()); parameters.setStoragePoolId(getStoragePoolId()); parameters.setStorageDomainId(getStorageDomainId()); parameters.setVirtioIsoPath(getVirtioIsoPath()); parameters.setCompatVersion(getParameters().getCompatVersion()); return parameters; } protected String getVirtioIsoPath() { return getParameters().getVirtioIsoName() == null ? null : new File(getIsoPrefix(getStoragePoolId(), getVdsId()), getParameters().getVirtioIsoName()).getPath(); } //////////////////// //// Async Part //// //////////////////// @Override protected void endSuccessfully() { getReturnValue().setEndActionTryAgain(false); try { if (getParameters().getOriginType() != OriginType.KVM) { VM vm = readVmFromOvf(getOvfOfConvertedVm()); updateBootDiskFlag(vm); addImportedDevices(vm); } setSucceeded(true); } catch (EngineException e) { log.info("failed to add devices to converted vm"); removeVm(); } finally { deleteV2VJob(); } } @Override protected void endWithFailure() { auditLog(this, AuditLogType.IMPORTEXPORT_CONVERT_FAILED); removeVm(); deleteV2VJob(); setSucceeded(true); } private VM readVmFromOvf(String ovf) { try { return ovfHelper.readVmFromOvf(ovf); } catch (OvfReaderException e) { log.debug("failed to parse a given ovf configuration: \n " + ovf, e); auditLog(this, AuditLogType.IMPORTEXPORT_INVALID_OVF); throw new EngineException(); } } private String getOvfOfConvertedVm() { VDSReturnValue retValue = runVdsCommand( VDSCommandType.GetConvertedOvf, new VdsAndVmIDVDSParametersBase(getVdsId(), getVmId())); if (!retValue.getSucceeded()) { auditLog(this, AuditLogType.IMPORTEXPORT_CANNOT_GET_OVF); throw new EngineException(); } return (String) retValue.getReturnValue(); } private void deleteV2VJob() { getVdsManager().removeV2VJobInfoForVm(getVmId()); getVmManager().setConvertProxyHostId(null); runVdsCommand( VDSCommandType.DeleteV2VJob, new VdsAndVmIDVDSParametersBase(getVdsId(), getVmId())); } private void updateBootDiskFlag(VM vm) { vm.getStaticData().getImages().stream().filter(disk -> disk.getDiskVmElementForVm(vm.getId()).isBoot()) .forEach(disk -> diskVmElementDao.update(disk.getDiskVmElementForVm(vm.getId()))); } private void addImportedDevices(VM vm) { VmStatic vmStatic = vm.getStaticData(); // Disk and network interface devices were already added vmStatic.setImages(new ArrayList<>()); vmStatic.setInterfaces(new ArrayList<>()); ImportUtils.updateGraphicsDevices(vmStatic, getStoragePool().getCompatibilityVersion()); getVmDeviceUtils().addImportedDevices(vmStatic, false); saveDiskVmElements(vm); } private void saveDiskVmElements(VM vm) { for (DiskImage disk : vm.getStaticData().getImages()) { diskVmElementDao.save(disk.getDiskVmElementForVm(vm.getId())); } } private void removeVm() { runInternalAction( VdcActionType.RemoveVm, new RemoveVmParameters(getVmId(), true)); } ///////////////////////// //// Utility Methods //// ///////////////////////// protected VmManager getVmManager() { return getResourceManager().getVmManager(getVmId()); } protected VdsManager getVdsManager() { return getResourceManager().getVdsManager(getVdsId()); } protected ResourceManager getResourceManager() { return resourceManager; } private CommandExecutionStatus getCommandExecutionStatus() { return CommandCoordinatorUtil.getCommandExecutionStatus(getCommandId()); } }