package org.ovirt.engine.core.bll; import java.util.List; import org.ovirt.engine.core.bll.command.utils.StorageDomainSpaceChecker; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.HibernateVmParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.asynctasks.AsyncTaskCreationInfo; import org.ovirt.engine.core.common.asynctasks.AsyncTaskParameters; import org.ovirt.engine.core.common.asynctasks.AsyncTaskType; import org.ovirt.engine.core.common.businessentities.AsyncTaskResultEnum; import org.ovirt.engine.core.common.businessentities.AsyncTaskStatusEnum; import org.ovirt.engine.core.common.businessentities.DiskType; import org.ovirt.engine.core.common.businessentities.StorageDomainType; import org.ovirt.engine.core.common.businessentities.StorageType; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VolumeFormat; import org.ovirt.engine.core.common.businessentities.VolumeType; import org.ovirt.engine.core.common.businessentities.async_tasks; import org.ovirt.engine.core.common.businessentities.storage_domain_static; import org.ovirt.engine.core.common.businessentities.storage_domains; import org.ovirt.engine.core.common.errors.VdcBLLException; import org.ovirt.engine.core.common.vdscommands.CreateImageVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.HibernateVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.UpdateVmDynamicDataVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.NGuid; import org.ovirt.engine.core.dal.VdcBllMessages; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.linq.LinqUtils; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class HibernateVmCommand<T extends HibernateVmParameters> extends VmOperationCommandBase<T> { private boolean isHibernateVdsProblematic = false; /** * Constructor for command creation when compensation is applied on startup * * @param commandId */ protected HibernateVmCommand(Guid commandId) { super(commandId); } public HibernateVmCommand(T parameters) { super(parameters); super.setStoragePoolId(getVm().getstorage_pool_id()); parameters.setEntityId(getVm().getvm_guid()); } private Guid _storageDomainId = Guid.Empty; @Override public NGuid getStorageDomainId() { if (_storageDomainId.equals(Guid.Empty) && getVm() != null) { VmHandler.updateDisksFromDb(getVm()); if (getVm().getDiskMap().size() > 0) { // LINQ 29456 // _storageDomainId = // Vm.DiskMap.Values.First().storage_id.Value; _storageDomainId = LinqUtils.first(getVm().getDiskMap().values()).getstorage_id().getValue(); } else { List<storage_domain_static> domainsInPool = DbFacade.getInstance() .getStorageDomainStaticDAO().getAllForStoragePool(getVm().getstorage_pool_id()); if (domainsInPool.size() > 0) { for (storage_domain_static currDomain : domainsInPool) { if (currDomain.getstorage_domain_type().equals(StorageDomainType.Master) || currDomain.getstorage_domain_type().equals(StorageDomainType.Data)) { _storageDomainId = currDomain.getId(); break; } } } } } return _storageDomainId; } @Override protected void Perform() { // Set the VM to null, to fetch it again from the DB ,instead from the cache. // We want to get the VM state from the DB, to avoid multi requests for VM hibernation. setVm(null); if (VM.isStatusUp(getVm().getstatus())) { TransactionSupport.executeInNewTransaction( new TransactionMethod<Object>() { @Override public Object runInTransaction() { getCompensationContext().snapshotEntityStatus(getVm().getDynamicData(), getVm().getstatus()); // Set the VM to SavingState to lock the VM,to avoid situation of multi VM hibernation. getVm().setstatus(VMStatus.SavingState); Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.UpdateVmDynamicData, new UpdateVmDynamicDataVDSCommandParameters(getVdsId(), getVm().getDynamicData())); getCompensationContext().stateChanged(); return null; } }); Guid image1GroupId = Guid.NewGuid(); // this is temp code until SPM will implement the new verb that does // it for us: Guid hiberVol1 = Guid.NewGuid(); VDSReturnValue ret1 = Backend .getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.CreateImage, new CreateImageVDSCommandParameters( getVm().getstorage_pool_id(), getStorageDomainId().getValue(), image1GroupId, getImageSizeInBytes(), getVolumeType(), VolumeFormat.RAW, DiskType.Data, hiberVol1, "", getStoragePool().getcompatibility_version().toString())); if (!ret1.getSucceeded()) { return; } Guid guid1 = CreateTask(ret1.getCreationInfo(), VdcActionType.HibernateVm); getReturnValue().getTaskIdList().add(guid1); // second vol should be 10kb Guid image2GroupId = Guid.NewGuid(); Guid hiberVol2 = Guid.NewGuid(); VDSReturnValue ret2 = Backend .getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.CreateImage, new CreateImageVDSCommandParameters(getVm().getstorage_pool_id(), getStorageDomainId() .getValue(), image2GroupId, getMetaDataSizeInBytes(), VolumeType.Sparse, VolumeFormat.COW, DiskType.Data, hiberVol2, "", getStoragePool().getcompatibility_version() .toString())); if (!ret2.getSucceeded()) { return; } Guid guid2 = CreateTask(ret2.getCreationInfo(), VdcActionType.HibernateVm); getReturnValue().getTaskIdList().add(guid2); // this is the new param that should be passed to the hibernate // command getVm().sethibernation_vol_handle( String.format("%1$s,%2$s,%3$s,%4$s,%5$s,%6$s", getStorageDomainId().toString(), getVm() .getstorage_pool_id().toString(), image1GroupId.toString(), hiberVol1.toString(), image2GroupId.toString(), hiberVol2.toString())); // end of temp code Backend.getInstance() .getResourceManager() .RunVdsCommand(VDSCommandType.UpdateVmDynamicData, new UpdateVmDynamicDataVDSCommandParameters(getVdsId(), getVm().getDynamicData())); getParameters().setTaskIds(new java.util.ArrayList<Guid>()); getParameters().getTaskIds().add(guid1); getParameters().getTaskIds().add(guid2); setSucceeded(true); } } @Override protected Guid ConcreteCreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) { AsyncTaskParameters p = new AsyncTaskParameters(asyncTaskCreationInfo, new async_tasks(parentCommand, AsyncTaskResultEnum.success, AsyncTaskStatusEnum.running, asyncTaskCreationInfo.getTaskID(), getParameters())); p.setEntityId(getParameters().getEntityId()); Guid taskID = AsyncTaskManager.getInstance().CreateTask(AsyncTaskType.createVolume, p, false); return taskID; } protected HibernateVmParameters getHibernateVmParams() { VdcActionParametersBase tempVar = getParameters(); return (HibernateVmParameters) ((tempVar instanceof HibernateVmParameters) ? tempVar : null); } @Override public AuditLogType getAuditLogTypeValue() { switch (getActionState()) { case EXECUTE: return getHibernateVmParams().getAutomaticSuspend() ? getSucceeded() ? AuditLogType.AUTO_SUSPEND_VM : AuditLogType.AUTO_FAILED_SUSPEND_VM : getSucceeded() ? AuditLogType.USER_SUSPEND_VM : AuditLogType.USER_FAILED_SUSPEND_VM; case END_SUCCESS: return getHibernateVmParams().getAutomaticSuspend() ? getSucceeded() ? AuditLogType.AUTO_SUSPEND_VM_FINISH_SUCCESS : AuditLogType.AUTO_SUSPEND_VM_FINISH_FAILURE : getSucceeded() ? AuditLogType.USER_SUSPEND_VM_FINISH_SUCCESS : isHibernateVdsProblematic ? AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE_WILL_TRY_AGAIN : AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE; default: return getHibernateVmParams().getAutomaticSuspend() ? AuditLogType.AUTO_SUSPEND_VM_FINISH_FAILURE : isHibernateVdsProblematic ? AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE_WILL_TRY_AGAIN : AuditLogType.USER_SUSPEND_VM_FINISH_FAILURE; } } @Override protected boolean canDoAction() { boolean retValue = true; if (getVm() == null) { retValue = false; addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_VM_NOT_FOUND); } // else if (IrsClusterMonitor.Instance.DiskFreePercent < // Config.FreeSpaceLow) // { // retValue = false; // ReturnValue.CanDoActionMessages.Add(VdcBllMessages.ACTION_TYPE_FAILED_DISK_SPACE_LOW.toString()); // } else if (getStorageDomainId().equals(Guid.Empty)) { addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_STORAGE_DOMAIN_NOT_EXIST); retValue = false; } else { if (getVm().getstatus() == VMStatus.WaitForLaunch || getVm().getstatus() == VMStatus.NotResponding) { retValue = false; addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL); } else if (getVm().getstatus() != VMStatus.Up) { retValue = false; getReturnValue().getCanDoActionMessages() .add(VdcBllMessages.ACTION_TYPE_FAILED_VM_IS_NOT_UP.toString()); } else { if (AsyncTaskManager.getInstance().EntityHasTasks(getVmId())) { retValue = false; addCanDoActionMessage(VdcBllMessages.VM_CANNOT_SUSPENDE_HAS_RUNNING_TASKS); } if (retValue) { // check if vm has stateless images in db in case vm was run once as stateless // (then is_stateless is false) if (getVm().getis_stateless() || !DbFacade.getInstance() .getDiskImageDAO() .getAllStatelessVmImageMapsForVm(getVmId()) .isEmpty()) { retValue = false; addCanDoActionMessage(VdcBllMessages.VM_CANNOT_SUSPEND_STATELESS_VM); } else if (DbFacade.getInstance().getVmPoolDAO().getVmPoolMapByVmGuid(getVmId()) != null) { retValue = false; addCanDoActionMessage(VdcBllMessages.VM_CANNOT_SUSPEND_VM_FROM_POOL); } // Check storage before trying to create Images for hibernation. storage_domains domain = DbFacade.getInstance().getStorageDomainDAO().get(getStorageDomainId().getValue()); if (retValue && !StorageDomainSpaceChecker.hasSpaceForRequest(domain, (getImageSizeInBytes() + getMetaDataSizeInBytes())/BYTES_IN_GB)) { retValue = false; addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_DISK_SPACE_LOW); } } } } if (!retValue) { addCanDoActionMessage(VdcBllMessages.VAR__TYPE__VM); addCanDoActionMessage(VdcBllMessages.VAR__ACTION__HIBERNATE); } return retValue; } @Override protected void EndSuccessfully() { if (getVm() != null) { if (getVm().getstatus() != VMStatus.SavingState && getVm().getstatus() != VMStatus.Up) { // If the Vm is not in SavingState/Up status, we shouldn't // perform Hibernate on it, // since if the Vm is in another status, something might have // happend to it // that might prevent it from being hibernated. // NOTE: We don't remove the 2 volumes because we don't want to // start here // another tasks. log.warnFormat( "HibernateVmCommand::EndSuccessfully: Vm '{0}' is not in 'SavingState'/'Up' status, but in '{1}' status - not performing Hibernate.", getVm().getvm_name(), getVm().getstatus()); getReturnValue().setEndActionTryAgain(false); } else if (getVm().getrun_on_vds() == null) { log.warnFormat( "HibernateVmCommand::EndSuccessfully: Vm '{0}' doesn't have 'run_on_vds' value - cannot Hibernate.", getVm().getvm_name()); getReturnValue().setEndActionTryAgain(false); } else { String hiberVol = getVm().gethibernation_vol_handle(); if (hiberVol != null) { try { Backend.getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.Hibernate, new HibernateVDSCommandParameters(new Guid(getVm().getrun_on_vds().toString()), getVmId(), getVm().gethibernation_vol_handle())); } catch (VdcBLLException e) { isHibernateVdsProblematic = true; throw e; } setSucceeded(true); } else { log.errorFormat("hibernation volume of VM '{0}', is not initialized.", getVm().getvm_name()); EndWithFailure(); } } } else { setCommandShouldBeLogged(false); log.warn("HibernateVmCommand::EndSuccessfully: Vm is null - not performing full EndAction."); setSucceeded(true); } } @Override protected void EndWithFailure() { if (getVm() != null) { RevertTasks(); if (getVm().getrun_on_vds() != null) { getVm().sethibernation_vol_handle(null); getVm().setstatus(VMStatus.Up); Backend.getInstance() .getResourceManager() .RunVdsCommand( VDSCommandType.UpdateVmDynamicData, new UpdateVmDynamicDataVDSCommandParameters( new Guid(getVm().getrun_on_vds().toString()), getVm().getDynamicData())); setSucceeded(true); } else { log.warnFormat( "HibernateVmCommand::EndWithFailure: Vm '{0}' doesn't have 'run_on_vds' value - not clearing 'hibernation_vol_handle' info.", getVm().getvm_name()); getReturnValue().setEndActionTryAgain(false); } } else { setCommandShouldBeLogged(false); log.warn("HibernateVmCommand::EndWithFailure: Vm is null - not performing full EndAction."); setSucceeded(true); } } /** * Returns whether to use Sparse or Preallocation. If the storage type is file system devices ,it would be more * efficient to use Sparse allocation. Otherwise for block devices we should use Preallocated for faster allocation. * * @return - VolumeType of allocation type to use. */ private VolumeType getVolumeType() { return (getStoragePool().getstorage_pool_type() == StorageType.NFS || getStoragePool().getstorage_pool_type() == StorageType.LOCALFS) ? VolumeType.Sparse : VolumeType.Preallocated; } /** * Returns the memory size should be allocated in the storage. * * @return - Memory size for allocation in bytes. */ private long getImageSizeInBytes() { return (long) (getVm().getvm_mem_size_mb() + 200 + (64 * getVm().getnum_of_monitors())) * 1024 * 1024; } /** * Returns the meta data that should be allocated when saving state of image. * * @return - Meta data size for allocation in bytes. */ private long getMetaDataSizeInBytes() { return (long) 10 * 1024; } private static LogCompat log = LogFactoryCompat.getLog(HibernateVmCommand.class); }