package org.ovirt.engine.core.bll; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.ejb.TransactionRolledbackLocalException; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.groups.Default; import org.apache.commons.lang.exception.ExceptionUtils; import org.ovirt.engine.core.bll.context.CompensationContext; import org.ovirt.engine.core.bll.context.DefaultCompensationContext; import org.ovirt.engine.core.bll.context.NoOpCompensationContext; import org.ovirt.engine.core.bll.session.SessionDataContainer; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.asynctasks.AsyncTaskCreationInfo; import org.ovirt.engine.core.common.businessentities.AsyncTaskStatus; import org.ovirt.engine.core.common.businessentities.BusinessEntity; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.EntityStatusSnapshot; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.action_version_map; import org.ovirt.engine.core.common.businessentities.tags; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.errors.VdcBLLException; import org.ovirt.engine.core.common.errors.VdcBllErrors; import org.ovirt.engine.core.common.errors.VdcFault; import org.ovirt.engine.core.common.interfaces.IBackendCallBackServer; import org.ovirt.engine.core.common.interfaces.IVdcUser; import org.ovirt.engine.core.common.vdscommands.SPMTaskGuidBaseVDSCommandParameters; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSParametersBase; 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.compat.NotImplementedException; import org.ovirt.engine.core.compat.StringHelper; import org.ovirt.engine.core.compat.TimeSpan; import org.ovirt.engine.core.compat.TransactionScopeOption; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.compat.backendcompat.PropertyInfo; import org.ovirt.engine.core.compat.backendcompat.TypeCompat; import org.ovirt.engine.core.dal.VdcBllMessages; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.dal.dbbroker.generic.RepositoryException; import org.ovirt.engine.core.dao.BusinessEntitySnapshotDAO; import org.ovirt.engine.core.dao.GenericDao; import org.ovirt.engine.core.dao.StatusAwareDao; import org.ovirt.engine.core.utils.Deserializer; import org.ovirt.engine.core.utils.ReflectionUtils; import org.ovirt.engine.core.utils.SerializationFactory; import org.ovirt.engine.core.utils.ThreadLocalParamsContainer; import org.ovirt.engine.core.utils.lock.EngineLock; import org.ovirt.engine.core.utils.lock.LockManagerFactory; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; import org.ovirt.engine.core.utils.transaction.RollbackHandler; import org.ovirt.engine.core.utils.transaction.TransactionMethod; import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.springframework.dao.DataAccessException; public abstract class CommandBase<T extends VdcActionParametersBase> extends AuditLogableBase implements RollbackHandler, TransactionMethod<Object> { /** * Multiplier used to convert GB to bytes or vice versa. */ protected static final long BYTES_IN_GB = 1024 * 1024 * 1024; private T _parameters; private VdcReturnValueBase _returnValue; private final IBackendCallBackServer _backendCallBack = CallbackServer.Instance; private CommandActionState _actionState = CommandActionState.forValue(0); private boolean isInternalExecution = false; private VdcActionType actionType; /** * According to hibernate validator documentation it is safe to assume it is * thread-safe.So holding one is encouraged. */ private final static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); private final List<Class<?>> validationGroups = new ArrayList<Class<?>>(Arrays.asList(new Class<?>[] { Default.class })); private CompensationContext compensationContext; private Guid commandId = Guid.NewGuid(); protected LogCompat log = LogFactoryCompat.getLog(getClass()); protected CommandActionState getActionState() { return _actionState; } protected CommandBase() { } protected CommandBase(T parameters) { _parameters = parameters; setCurrentUser(addUserToThredContext(parameters.getSessionId())); } /** * Constructor for command creation when compensation is applied on startup * * @param commandId */ protected CommandBase(Guid commandId) { this.commandId = commandId; } /** * Create an appropriate compensation context. The default is one that does nothing for command that don't run in a * transaction, and a real one for commands that run in a transaction. * * @param transactionScopeOption * The transaction scope. * @param forceCompensation * @return The compensation context to use. */ private CompensationContext createCompensationContext(TransactionScopeOption transactionScopeOption, boolean forceCompensation) { if (transactionScopeOption == TransactionScopeOption.Suppress && !forceCompensation) { return new NoOpCompensationContext(); } DefaultCompensationContext context = new DefaultCompensationContext(); context.setCommandId(commandId); context.setCommandType(getClass().getName()); context.setBusinessEntitySnapshotDAO(getBusinessEntitySnapshotDAO()); context.setSnapshotSerializer( SerializationFactory.getFactory().createSerializer()); return context; } protected BusinessEntitySnapshotDAO getBusinessEntitySnapshotDAO() { return DbFacade.getInstance().getBusinessEntitySnapshotDAO(); } /** * @return the compensationContext */ protected CompensationContext getCompensationContext() { return compensationContext; } /** * @param compensationContext the compensationContext to set */ protected void setCompensationContext(CompensationContext compensationContext) { this.compensationContext = compensationContext; } /** * This method will add a user to thread local, at case that user is not * already added to context. If session is null or empty will try to get * session from thread local * * @param sessionId * -id of session */ private IVdcUser addUserToThredContext(String sessionId) { IVdcUser vdcUser = ThreadLocalParamsContainer.getVdcUser(); if (vdcUser == null) { if (!StringHelper.isNullOrEmpty(sessionId)) { vdcUser = (IVdcUser) SessionDataContainer.getInstance().GetData(sessionId, "VdcUser"); ThreadLocalParamsContainer.setHttpSessionId(sessionId); } else { vdcUser = (IVdcUser) SessionDataContainer.getInstance().GetData("VdcUser"); } ThreadLocalParamsContainer.setVdcUser(vdcUser); } return vdcUser; } private String _description = ""; private TransactionScopeOption scope; private TransactionScopeOption endActionScope; public VdcReturnValueBase CanDoActionOnly() { setActionMessageParameters(); getReturnValue().setCanDoAction(InternalCanDoAction()); String tempVar = getDescription(); getReturnValue().setDescription((tempVar != null) ? tempVar : getReturnValue().getDescription()); return _returnValue; } public VdcReturnValueBase ExecuteAction() { _actionState = CommandActionState.EXECUTE; String tempVar = getDescription(); getReturnValue().setDescription((tempVar != null) ? tempVar : getReturnValue().getDescription()); setActionMessageParameters(); if (acquireLock() && (getReturnValue().getCanDoAction() || InternalCanDoAction())) { getReturnValue().setCanDoAction(true); getReturnValue().setIsSyncronious(true); getParameters().setTaskStartTime(System.currentTimeMillis()); Execute(); } else { getReturnValue().setCanDoAction(false); } return getReturnValue(); } /** * Run the default compensation logic (inside a new transaction):<br> * <ol> * <li>Get all the entity snapshots that this command has created.</li> * <li>For each snapshot:</li> * <ol> * <li>Deserialize the entity.</li> * <li>Using the entity DAO:</li> * <ul> * <li>If the entity was added by the command, remove it.</li> * <li>Otherwise, If the entity is not in DB anymore, restore it.</li> * <li>Otherwise, update it.</li> * </ul> * </ol> * <li>Remove all the snapshots for this command, since we handled them.</li> </ol> */ @SuppressWarnings("unchecked") protected void compensate() { TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() { @Override public Object runInTransaction() { Deserializer deserializer = SerializationFactory.getFactory().createDeserializer(); List<BusinessEntitySnapshot> entitySnapshots = getBusinessEntitySnapshotDAO().getAllForCommandId(commandId); log.debugFormat("Command [id={0}]: {1} compensation data.", commandId, entitySnapshots.isEmpty() ? "No" : "Going over"); for (BusinessEntitySnapshot snapshot : entitySnapshots) { Class<Serializable> snapshotClass = (Class<Serializable>)ReflectionUtils.getClassFor(snapshot.getSnapshotClass()); Serializable snapshotData = deserializer.deserialize(snapshot.getEntitySnapshot(), snapshotClass); log.infoFormat("Command [id={0}]: Compensating {1} of {2}; snapshot: {3}.", commandId, snapshot.getSnapshotType(), snapshot.getEntityType(), (snapshot.getSnapshotType() == SnapshotType.CHANGED_ENTITY ? "id=" + snapshot.getEntityId() : snapshotData.toString())); Class<BusinessEntity<Serializable>> entityClass = (Class<BusinessEntity<Serializable>>) ReflectionUtils.getClassFor(snapshot.getEntityType()); GenericDao<BusinessEntity<Serializable>, Serializable> daoForEntity = DbFacade.getInstance().getDaoForEntity(entityClass); switch (snapshot.getSnapshotType()) { case CHANGED_STATUS_ONLY: EntityStatusSnapshot entityStatusSnapshot = (EntityStatusSnapshot) snapshotData; ((StatusAwareDao<Serializable, Enum<?>>) daoForEntity).updateStatus( entityStatusSnapshot.getId(), entityStatusSnapshot.getStatus()); break; case CHANGED_ENTITY: BusinessEntity<Serializable> entitySnapshot = (BusinessEntity<Serializable>) snapshotData; if (daoForEntity.get(entitySnapshot.getId()) == null) { daoForEntity.save(entitySnapshot); } else { daoForEntity.update(entitySnapshot); } break; case NEW_ENTITY_ID: daoForEntity.remove(snapshotData); break; } } cleanUpCompensationData(); return null; } }); } /** * Delete the compensation data, so that we don't accidentaly try to compensate it at a later time. */ private void cleanUpCompensationData() { getBusinessEntitySnapshotDAO().removeAllForCommandId(commandId); } public VdcReturnValueBase EndAction() { try { SetActionState(); handleTransactivity(); TransactionSupport.executeInScope(endActionScope, this); } catch (TransactionRolledbackLocalException e) { log.infoFormat("EndAction: Transaction was aborted in {0}", this.getClass().getName()); } finally { if (getCommandShouldBeLogged()) { LogCommand(); } } return getReturnValue(); } private void handleTransactivity() { scope = (getParameters() != null) ? getParameters().getTransactionScopeOption() : TransactionScopeOption.Required; endActionScope = scope; boolean forceCompensation = getForceCompensation(); // @NonTransactiveAttribute annotation overrides the scope passed by the // command parameters if (!getTransactive()) { scope = TransactionScopeOption.Suppress; // Set the end action scope to suppress only for non-compensating commands, or the end action for commands // will run without transaction but compensation is not supported for end action. endActionScope = forceCompensation ? endActionScope : scope; } if (compensationContext == null) { compensationContext = createCompensationContext(scope, forceCompensation); } } private void SetActionState() { if (getParameters().getTaskGroupSuccess()) { _actionState = CommandActionState.END_SUCCESS; } else { _actionState = CommandActionState.END_FAILURE; } } public void endActionInTransactionScope() { try { if (getParameters().getTaskGroupSuccess()) { InternalEndSuccessfully(); } else { InternalEndWithFailure(); } } finally { if (TransactionSupport.current() == null) { cleanUpCompensationData(); } else { try { if (TransactionSupport.current().getStatus() == Status.STATUS_ACTIVE) { cleanUpCompensationData(); } else { compensate(); } } catch (SystemException e) { log.errorFormat("Exception while wrapping-up compensation in endAction: {0}.", ExceptionUtils.getMessage(e), e); compensate(); } } } } private void InternalEndSuccessfully() { log.infoFormat("Ending command successfully: {0}", getClass().getName()); EndSuccessfully(); } protected void EndSuccessfully() { setSucceeded(true); } private void InternalEndWithFailure() { log.errorFormat("Ending command with failure: {0}", getClass().getName()); EndWithFailure(); } protected void EndWithFailure() { setSucceeded(true); } private boolean InternalCanDoAction() { try { boolean returnValue; Transaction transaction = TransactionSupport.suspend(); try { returnValue = IsUserAutorizedToRunAction() && IsBackwardsCompatible() && validateInputs() && canDoAction(); if (!returnValue && getReturnValue().getCanDoActionMessages().size() > 0) { log.warnFormat("CanDoAction of action {0} failed. Reasons:{1}", getActionType(), StringHelper.aggregate(getReturnValue().getCanDoActionMessages(), ',')); } } finally { TransactionSupport.resume(transaction); } return returnValue; } catch (DataAccessException dataAccessEx) { log.error("Data access error during CanDoActionFailure.", dataAccessEx); addCanDoActionMessage(VdcBllMessages.CAN_DO_ACTION_DATABASE_CONNECTION_FAILURE); return false; } catch (RuntimeException ex) { log.error("Error during CanDoActionFailure.", ex); addCanDoActionMessage(VdcBllMessages.CAN_DO_ACTION_GENERAL_FAILURE); return false; } } /** * @return true if all parameters class and its inner members passed * validation */ protected boolean validateInputs() { List<Class<?>> validationGroupList = getValidationGroups(); Set<ConstraintViolation<T>> violations = validator.validate(getParameters(), ((Class<?>[]) validationGroupList.toArray(new Class<?>[validationGroupList.size()]))); if (!violations.isEmpty()) { ArrayList<String> msgs = getReturnValue().getCanDoActionMessages(); for (ConstraintViolation<T> constraintViolation : violations) { msgs.add(constraintViolation.getMessage()); } return false; } return true; } /** * Set the parameters for bll messages (such as type and action). * The parameters should be initialized through the command that is called, * instead set them at the canDoAction() */ protected void setActionMessageParameters() { } protected List<Class<?>> getValidationGroups() { return validationGroups; }; protected List<Class<?>> addValidationGroup(Class<?>... validationGroup) { validationGroups.addAll(Arrays.asList(validationGroup)); return validationGroups; } protected boolean IsBackwardsCompatible() { boolean result = true; action_version_map actionVersionMap = DbFacade.getInstance() .getActionGroupDAO().getActionVersionMapByActionType(getActionType()); // if actionVersionMap not null check cluster level // cluster level ok check storage_pool level if (actionVersionMap != null && ((getVdsGroup() != null && getVdsGroup().getcompatibility_version().compareTo( new Version(actionVersionMap.getcluster_minimal_version())) < 0) || (!StringHelper.EqOp( actionVersionMap.getstorage_pool_minimal_version(), "*") && getStoragePool() != null && getStoragePool() .getcompatibility_version().compareTo( new Version(actionVersionMap.getstorage_pool_minimal_version())) < 0))) { result = false; addCanDoActionMessage(VdcBllMessages.ACTION_NOT_SUPPORTED_FOR_CLUSTER_POOL_LEVEL); } return result; } /** * Check if current user is authorized to run current action. skip check if * MLA is off or command is internal * * @return */ protected boolean IsUserAutorizedToRunAction() { boolean returnValue = true; // skip internal actions and MLA is off if (isInternalExecution || !Config.<Boolean> GetValue(ConfigValues.IsMultilevelAdministrationOn)) { if (log.isDebugEnabled()) { log.debugFormat( "IsUserAutorizedToRunAction: Internal action or MLA is off - permission check skipped for action {0}", getActionType()); } } // check ActionGroup defined for this action else if (getActionType().getActionGroup() == null) { if (log.isDebugEnabled()) { returnValue = false; log.debugFormat( "IsUserAutorizedToRunAction: No ActionGroup defiend for action {0} - check cannot proceed", getActionType().toString()); } } else if (getCurrentUser() != null) { // get subjects to check permissions on Map<Guid, VdcObjectType> permSubjects = getPermissionCheckSubjects(); if (permSubjects == null || permSubjects.isEmpty()) { returnValue = false; if (log.isDebugEnabled()) { log.debugFormat( "IsUserAutorizedToRunAction: PermissionCheckSubjects is null or empty for action {0}", getActionType()); } } else { for (Map.Entry<Guid, VdcObjectType> entry : permSubjects.entrySet()) { // if objectId is null we can't check permission if (entry.getKey() == null) { returnValue = false; if (log.isDebugEnabled()) { log.debugFormat( "IsUserAutorizedToRunAction: Object from PermissionCheckSubjects is null for action {0} permission check failed.", getActionType()); } break; } NGuid permId = DbFacade.getInstance().getEntityPermissions(getCurrentUser().getUserId(), getActionType().getActionGroup(), entry.getKey(), entry.getValue()); if (permId != null) { if (log.isDebugEnabled()) { log.debugFormat( "IsUserAutorizedToRunAction: found permission {0} for user when running {1}, on {2} with id {3}", permId, getActionType(), entry.getValue().getVdcObjectTranslation(), entry.getKey()); } } else { returnValue = false; if (log.isDebugEnabled()) { log.debugFormat( "IsUserAutorizedToRunAction: no permission found for user when running {0}, on {1} with id {2}", getActionType(), entry.getValue().getVdcObjectTranslation(), entry.getKey()); } break; } } } } else { addCanDoActionMessage(VdcBllMessages.USER_IS_NOT_LOGGED_IN); return false; } if (returnValue == false) { addCanDoActionMessage(VdcBllMessages.USER_NOT_AUTHORIZED_TO_PERFORM_ACTION); } return returnValue; } protected List<tags> GetTagsAttachedToObject() { // tags_permissions_map return new java.util.ArrayList<tags>(); } protected boolean canDoAction() { return true; } /** * Factory to determine the type of the ReturnValue field * * @return */ protected VdcReturnValueBase CreateReturnValue() { return new VdcReturnValueBase(); } protected boolean getSucceeded() { return getReturnValue().getSucceeded(); } protected void setSucceeded(boolean value) { getReturnValue().setSucceeded(value); } public boolean getCommandShouldBeLogged() { return getParameters().getShouldBeLogged(); } public void setCommandShouldBeLogged(boolean value) { getParameters().setShouldBeLogged(value); } protected void setActionReturnValue(Object value) { getReturnValue().setActionReturnValue(value); } protected Object getActionReturnValue() { return getReturnValue().getActionReturnValue(); } public TimeSpan getTransactionTimeout() { return new TimeSpan(1, 1, 0); } /** * Calculates the proper parameters for the task * @param parentCommandType parent command type for which the task is created * @param parameters parameter of the creating command * @return */ protected VdcActionParametersBase getParametersForTask(VdcActionType parentCommandType, VdcActionParametersBase parameters) { //If there is no parent command, the command that its type //will be stored in the DB for thr task is the one creating the command if (parentCommandType == VdcActionType.Unknown) { return parameters; } VdcActionParametersBase parentParameters = parameters.getParentParameters(); if (parentParameters == null) { String msg = "No parameters exist for " + parentCommandType; log.error(msg); throw new VdcBLLException(VdcBllErrors.NO_PARAMETERS_FOR_TASK,msg); } return parentParameters; } private boolean ExecuteWithoutTransaction() { boolean functionReturnValue = false; boolean exceptionOccurred = true; try { logRunningCommand(); executeCommand(); functionReturnValue = getSucceeded(); exceptionOccurred = false; } catch (RepositoryException e) { log.error(String.format("Command %1$s throw Database exception", getClass().getName()), e); ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.DB)); } // catch (LicenseException e) // { // log.error( // string.Format("Command {0} throw License exception", GetType().Name), // e); // ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.LICENSE)); // } catch (VdcBLLException e) { log.error(String.format("Command %1$s throw Vdc Bll exception. With error message %2$s", getClass().getName(), e.getMessage())); if (log.isDebugEnabled()) { log.debug(String.format("Command %1$s throw Vdc Bll exception", getClass().getName()), e); } ProcessExceptionToClient(new VdcFault(e, e.getVdsError().getCode())); } catch (RuntimeException e) { ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.ENGINE)); log.error(String.format("Command %1$s throw exception", getClass().getName()), e); } finally { // If we failed to execute due to exception or some other reason, we compensate for the failure. if (exceptionOccurred || !getSucceeded()) { compensate(); } else { cleanUpCompensationData(); } } return functionReturnValue; } protected TransactionScopeOption getTransactionScopeOption() { return getParameters().getTransactionScopeOption(); } /** * Log the running command , and log the affected entity id and type (if * there are any). */ private void logRunningCommand() { // Set start of log for running command. StringBuilder logInfo = new StringBuilder("Running command: ") .append(getClass().getSimpleName()).append(" internal: ") .append(isInternalExecution).append("."); // Get permissions of object ,to get object id. Map<Guid, VdcObjectType> permSubjects = getPermissionCheckSubjects(); // Log if there is entry in the permission map. if (permSubjects != null && !permSubjects.isEmpty()) { // Build entities string for entities affected by this operation. StringBuilder logEntityIdsInfo = new StringBuilder(); // Iterate all over the entities , which should be affected. for (Map.Entry<Guid, VdcObjectType> entry : permSubjects .entrySet()) { if (entry.getKey() != null) { // Add comma when there are more then one entity // affected. if (logEntityIdsInfo.length() != 0) { logEntityIdsInfo.append(", "); } logEntityIdsInfo.append(" ID: ").append(entry.getKey()) .append(" Type: ").append(entry.getValue()); } } // If found any entities, add the log to the logInfo. if (logEntityIdsInfo.length() != 0) { // Print all the entities affected. logInfo.append(" Entities affected : ").append( logEntityIdsInfo); } } // Log the final appended message to the log. log.info(logInfo); } private void executeActionInTransactionScope() { if (TransactionSupport.current() != null) { TransactionSupport.registerRollbackHandler(CommandBase.this); } // If we didn't managed to acquire lock for command or the object wasn't managed to execute properly, then // rollback the transaction. if (!ExecuteWithoutTransaction()) { if (TransactionSupport.current() == null) { cancelTasks(); } // we don't want to commit transaction here TransactionSupport.setRollbackOnly(); } } private void Execute() { try { handleTransactivity(); TransactionSupport.executeInScope(scope, this); } catch (TransactionRolledbackLocalException e) { log.infoFormat("Transaction was aborted in {0}", this.getClass().getName()); //Transaction was aborted - we must sure we compensation for all previous applicative stages of the command compensate(); } finally { freeLock(); if (getCommandShouldBeLogged()) { LogCommand(); } if (getSucceeded()) { // only after creating all tasks, we can start polling them (we // don't want // to start polling before all tasks were created, otherwise we // might change // the VM/VmTemplate status to 'Down'/'OK' too soon. UpdateTasksWithActionParameters(); StartPollingAsyncTasks(); } } } private boolean getForceCompensation() { NonTransactiveCommandAttribute annotation = getClass().getAnnotation(NonTransactiveCommandAttribute.class); return annotation != null && annotation.forceCompensation(); } protected abstract void executeCommand(); private void LogCommand() { Class<?> type = getClass(); // Object[] attributes = new Object[] {}; // FIXED // type.GetCustomAttributes(InternalCommandAttribute.class, false); InternalCommandAttribute annotation = type.getAnnotation(InternalCommandAttribute.class); if (annotation == null) { log(); } } private boolean getTransactive() { // Object[] attributes = new Object[] {}; // FIXED // getClass().GetCustomAttributes(NonTransactiveCommandAttribute.class, // true); NonTransactiveCommandAttribute annotation = getClass().getAnnotation(NonTransactiveCommandAttribute.class); return annotation == null; } protected static java.util.Date getNow() { long nowMiliSeconds = System.currentTimeMillis(); return new java.util.Date(nowMiliSeconds); } protected T getParameters() { return _parameters; } public VdcReturnValueBase getReturnValue() { if (_returnValue == null) { _returnValue = CreateReturnValue(); } return _returnValue; } protected VdcActionType getActionType() { try { if (actionType == null) { String name = getClass().getName(); name = name.substring(0, name.length() - 7); name = name.substring(name.lastIndexOf('.') + 1); actionType = VdcActionType.valueOf(name); } return actionType; } catch (java.lang.Exception e) { return VdcActionType.Unknown; } } protected String getDescription() { return _description; } protected void setDescription(String value) { _description = value; } private void ProcessExceptionToClient(VdcFault fault) { fault.setSessionID(getParameters().getSessionId()); if (getParameters().getMultipleAction()) { if (_backendCallBack != null) { try { Guid FaultQueryId = _backendCallBack.BackendException(getActionType(), fault); BackendCallBacksDirector.getInstance().RegisterFaultQuery(FaultQueryId, fault.getSessionID()); } catch (RuntimeException ex) { log.errorFormat("{0}", ex); } } } else { _returnValue.setFault(fault); } } /** * Use this method in order to create task in the AsyncTaskManager in a safe * way. If you use this method within a certain command, make sure that the * command implemented the ConcreteCreateTask method. * * @param asyncTaskCreationInfo * info to send to AsyncTaskManager when creating the task. * @param parentCommand * VdcActionType of the command that its EndAction we want to * invoke when tasks are finished. * @return Guid of the created task. */ protected Guid CreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) { Guid retValue = Guid.Empty; Transaction transaction = TransactionSupport.suspend(); try { try { retValue = ConcreteCreateTask(asyncTaskCreationInfo, parentCommand); } catch (RuntimeException ex) { log.errorFormat("Error during CreateTask for command: {0}. Exception {1}", getClass().getName(), ex); } } finally { TransactionSupport.resume(transaction); } return retValue; } protected Guid ConcreteCreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) { throw new NotImplementedException(); } protected void UpdateTasksWithActionParameters() { for (Guid taskID : getReturnValue().getTaskIdList()) { AsyncTaskManager.getInstance().UpdateTaskWithActionParameters(taskID, getParameters()); } } private void StartPollingAsyncTasks() { for (Guid taskID : getReturnValue().getTaskIdList()) { AsyncTaskManager.getInstance().StartPollingTask(taskID); } } protected java.util.ArrayList<Guid> getTaskIdList() { return getParameters().getParentCommand() != VdcActionType.Unknown ? getReturnValue().getInternalTaskIdList() : getReturnValue().getTaskIdList(); } @Override public void Rollback() { log.errorFormat("Transaction rolled-back for command: {0}.", CommandBase.this.getClass().getName()); cancelTasks(); } private void cancelTasks() { if (!getReturnValue().getTaskIdList().isEmpty()) { ThreadPoolUtil.execute(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); Thread.currentThread().setName("Rollback-" + threadName); TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() { @Override public Object runInTransaction() { try { AsyncTaskManager.getInstance().CancelTasks(getReturnValue().getTaskIdList()); } catch (java.lang.Exception e) { log.errorFormat("Failed to cancel tasks for command: {0}.", CommandBase.this.getClass().getName()); } return null; } }); } }); } } protected void RevertTasks() { if (getParameters().getTaskIds() != null) { // list to send to the PollTasks mathod java.util.ArrayList<Guid> taskIdAsList = new java.util.ArrayList<Guid>(); for (Guid taskId : getParameters().getTaskIds()) { taskIdAsList.add(taskId); java.util.ArrayList<AsyncTaskStatus> tasksStatuses = AsyncTaskManager.getInstance().PollTasks( taskIdAsList); // call revert task only if ended succeesfully if (tasksStatuses.get(0).getTaskEndedSuccessfully()) { SPMTaskGuidBaseVDSCommandParameters tempVar = new SPMTaskGuidBaseVDSCommandParameters( getStoragePool().getId(), taskId); tempVar.setCompatibilityVersion(getStoragePool().getcompatibility_version().toString()); Backend.getInstance().getResourceManager().RunVdsCommand(VDSCommandType.SPMRevertTask, tempVar); } taskIdAsList.clear(); } } } protected Guid getObjectLockingId() { Guid returnValue = Guid.Empty; Class<?> type = getClass(); LockIdNameAttribute annotation = type.getAnnotation(LockIdNameAttribute.class); if (annotation != null) { PropertyInfo propertyInfo = TypeCompat.GetProperty(type, annotation.fieldName()); if (propertyInfo != null) { returnValue = (Guid) propertyInfo.GetValue(this, Guid.Empty); } } return returnValue; } /** * Object which is representing a lock that some commands will acquire */ private EngineLock commandLock = null; protected boolean acquireLock() { Guid objectLockingId = getObjectLockingId(); boolean returnValue = true; if (!Guid.Empty.equals(objectLockingId)) { EngineLock lock = new EngineLock(); String currType = getClass().getName(); Map<String, Guid> exclusiveLock = Collections.singletonMap(currType, objectLockingId); lock.setExclusiveLocks(exclusiveLock); if (LockManagerFactory.getLockManager().acquireLock(lock)) { log.infoFormat("Lock Acquired to object {0}", lock); commandLock = lock; } else { log.infoFormat("Failed to Acquire Lock to object {0}", lock); addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_OBJECT_LOCKED); returnValue = false; } } return returnValue; } protected void freeLock() { if (commandLock != null) { LockManagerFactory.getLockManager().releaseLock(commandLock); log.infoFormat("Lock freed to object {0}", commandLock); commandLock = null; } } @Override public Object runInTransaction() { if (_actionState == CommandActionState.EXECUTE) { executeActionInTransactionScope(); return null; } else { endActionInTransactionScope(); return null; } } protected boolean isInternalExecution() { return isInternalExecution; } public void setInternalExecution(boolean isInternalExecution) { this.isInternalExecution = isInternalExecution; } /** * Add a message to the {@link CommandBase#canDoAction()}'s return value. * This return value will be sent to the client for the detailed information * of why the action can't be performed. * * @param message * The message to add. */ protected void addCanDoActionMessage(VdcBllMessages message) { getReturnValue().getCanDoActionMessages().add(message.name()); } /** * Add a message to the {@link CommandBase#canDoAction()}'s return value. * This return value will be sent to the client for the detailed information of why the action can't be performed. * * @param message The message to add. */ protected void addCanDoActionMessage(String message) { getReturnValue().getCanDoActionMessages().add(message); } /** * Run the given command in the VDS and return the VDS's response. * * @param commandType * The command to run. * @param parameters * The corresponding parameters for the command. * @return The return from the VDS, containing success/failure, async task ids (in case of success), or error data * (in case of failure). * @throws VdcBLLException * In case of an unhandled exception (Usually more severe than failure of the command, because we don't * know why). */ protected VDSReturnValue runVdsCommand(VDSCommandType commandType, VDSParametersBase parameters) throws VdcBLLException { return Backend.getInstance().getResourceManager().RunVdsCommand(commandType, parameters); } /** * Permissions are attached to object so every command must declare its * object target type and its GUID * * @return Map of Guids to Object types */ public abstract Map<Guid, VdcObjectType> getPermissionCheckSubjects(); }