package org.ovirt.engine.core.bll.gluster; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.gluster.CreateGlusterVolumeSnapshotParameters; import org.ovirt.engine.core.common.action.gluster.GlusterVolumeGeoRepSessionParameters; import org.ovirt.engine.core.common.asynctasks.gluster.GlusterTaskType; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.gluster.GeoRepSessionStatus; import org.ovirt.engine.core.common.businessentities.gluster.GlusterBrickEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterGeoRepSession; import org.ovirt.engine.core.common.businessentities.gluster.GlusterSnapshotStatus; import org.ovirt.engine.core.common.businessentities.gluster.GlusterStatus; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeSnapshotEntity; import org.ovirt.engine.core.common.constants.gluster.GlusterConstants; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.job.JobExecutionStatus; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.gluster.CreateGlusterVolumeSnapshotVDSParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.utils.lock.EngineLock; import org.ovirt.engine.core.utils.transaction.TransactionSupport; public class CreateGlusterVolumeSnapshotCommand extends GlusterSnapshotCommandBase<CreateGlusterVolumeSnapshotParameters> { private GlusterVolumeSnapshotEntity snapshot; private boolean force; private List<GlusterGeoRepSession> georepSessions; private List<GlusterGeoRepSession> enginePausedSessions; public CreateGlusterVolumeSnapshotCommand(CreateGlusterVolumeSnapshotParameters params, CommandContext commandContext) { super(params, commandContext); this.snapshot = params.getSnapshot(); this.force = params.getForce(); this.enginePausedSessions = new ArrayList<>(); } @Override protected void init() { super.init(); if (this.snapshot != null) { setClusterId(this.snapshot.getClusterId()); setGlusterVolumeId(snapshot.getVolumeId()); this.georepSessions = glusterGeoRepDao.getGeoRepSessions(getGlusterVolumeId()); } } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__CREATE); addCustomValue(GlusterConstants.VOLUME_SNAPSHOT_NAME, getParameters().getSnapshot().getSnapshotName()); super.setActionMessageParameters(); } private boolean pauseAndCreateSnapshotForGeoRepSessions() { if (georepSessions != null && georepSessions.size() > 0) { for (GlusterGeoRepSession session : georepSessions) { final GlusterVolumeEntity slaveVolume = glusterVolumeDao.getById(session.getSlaveVolumeId()); if (slaveVolume == null) { // Continue to other geo-rep sessions and pause them for snapshot purpose continue; } VDS slaveUpServer = glusterUtil.getRandomUpServer(slaveVolume.getClusterId()); if (slaveUpServer == null) { handleVdsError(AuditLogType.GLUSTER_VOLUME_SNAPSHOT_CREATE_FAILED, "No up server found in slave cluster of geo-rep session"); setSucceeded(false); return false; } // Pause the geo-rep session if required if (!(session.getStatus() == GeoRepSessionStatus.CREATED || session.getStatus() == GeoRepSessionStatus.PAUSED || session.getStatus() == GeoRepSessionStatus.STOPPED)) { VdcReturnValueBase sessionPauseRetVal = null; try (EngineLock lock = acquireEngineLock(slaveVolume.getId(), LockingGroup.GLUSTER_SNAPSHOT)) { sessionPauseRetVal = runInternalAction(VdcActionType.PauseGlusterVolumeGeoRepSession, new GlusterVolumeGeoRepSessionParameters(getGlusterVolumeId(), session.getId())); } if (sessionPauseRetVal != null && !sessionPauseRetVal.getSucceeded()) { handleVdsErrors(AuditLogType.GLUSTER_VOLUME_GEO_REP_PAUSE_FAILED, sessionPauseRetVal.getExecuteFailedMessages()); setSucceeded(false); return false; } session.setStatus(GeoRepSessionStatus.PAUSED); enginePausedSessions.add(session); } // Create snapshot for slave volume VDSReturnValue snapCreationRetVal = runVdsCommand(VDSCommandType.CreateGlusterVolumeSnapshot, new CreateGlusterVolumeSnapshotVDSParameters(slaveUpServer.getId(), session.getSlaveVolumeName(), snapshot.getSnapshotName(), snapshot.getDescription(), force)); if (!snapCreationRetVal.getSucceeded()) { handleVdsError(AuditLogType.GLUSTER_VOLUME_SNAPSHOT_CREATE_FAILED, snapCreationRetVal.getVdsError().getMessage()); setSucceeded(false); return false; } else { // Persist the snapshot details GlusterVolumeSnapshotEntity slaveVolumeSnapshot = (GlusterVolumeSnapshotEntity) snapCreationRetVal.getReturnValue(); slaveVolumeSnapshot.setClusterId(slaveVolume.getClusterId()); slaveVolumeSnapshot.setVolumeId(slaveVolume.getId()); slaveVolumeSnapshot.setDescription(snapshot.getDescription()); slaveVolumeSnapshot.setStatus(GlusterSnapshotStatus.DEACTIVATED); glusterVolumeSnapshotDao.save(slaveVolumeSnapshot); // check if the snapshot soft limit reached now for the volume and alert glusterUtil.alertVolumeSnapshotLimitsReached(slaveVolume); } } } return true; } @Override protected void executeCommand() { GlusterVolumeEntity volume = getGlusterVolume(); // Pause geo-rep sessions and create snapshot for slave volumes Boolean tranRetVal = TransactionSupport.executeInNewTransaction(() -> pauseAndCreateSnapshotForGeoRepSessions()); if (!tranRetVal) { return; } // Create snapshot for the master volume VDSReturnValue retVal = runVdsCommand(VDSCommandType.CreateGlusterVolumeSnapshot, new CreateGlusterVolumeSnapshotVDSParameters(upServer.getId(), volume.getName(), snapshot.getSnapshotName(), snapshot.getDescription(), force)); setSucceeded(retVal.getSucceeded()); if (!retVal.getSucceeded()) { handleVdsError(AuditLogType.GLUSTER_VOLUME_SNAPSHOT_CREATE_FAILED, retVal.getVdsError().getMessage()); } else { GlusterVolumeSnapshotEntity createdSnapshot = (GlusterVolumeSnapshotEntity) retVal.getReturnValue(); createdSnapshot.setClusterId(snapshot.getClusterId()); createdSnapshot.setVolumeId(snapshot.getVolumeId()); createdSnapshot.setDescription(snapshot.getDescription()); createdSnapshot.setStatus(GlusterSnapshotStatus.DEACTIVATED); glusterVolumeSnapshotDao.save(createdSnapshot); addCustomValue(GlusterConstants.VOLUME_SNAPSHOT_NAME, createdSnapshot.getSnapshotName()); // check if the snapshot soft limit reached now for the volume and alert glusterUtil.alertVolumeSnapshotLimitsReached(getGlusterVolume()); } // Resume the snapshot paused sessions by engine for (GlusterGeoRepSession session : enginePausedSessions) { if (session.getStatus() == GeoRepSessionStatus.PAUSED) { try (EngineLock lock = acquireGeoRepSessionLock(session.getId())) { VdcReturnValueBase sessionResumeRetVal = runInternalAction(VdcActionType.ResumeGeoRepSession, new GlusterVolumeGeoRepSessionParameters(volume.getId(), session.getId())); if (!sessionResumeRetVal.getSucceeded()) { handleVdsErrors(AuditLogType.GLUSTER_VOLUME_GEO_REP_RESUME_FAILED, sessionResumeRetVal.getExecuteFailedMessages()); setSucceeded(false); return; } } } } } @Override protected boolean validate() { if (!super.validate()) { return false; } GlusterVolumeEntity volume = getGlusterVolume(); if (volume.getStatus() == GlusterStatus.DOWN) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_GLUSTER_VOLUME_IS_DOWN); } if (volume.getAsyncTask() != null && (volume.getAsyncTask().getType() == GlusterTaskType.REBALANCE || volume.getAsyncTask().getType() == GlusterTaskType.REMOVE_BRICK) && volume.getAsyncTask().getStatus() == JobExecutionStatus.STARTED) { addValidationMessageVariable("asyncTask", volume.getAsyncTask().getType().name().toLowerCase()); return failValidation(EngineMessage.ACTION_TYPE_FAILED_VOLUME_ASYNC_OPERATION_IN_PROGRESS); } if (!glusterUtil.isVolumeThinlyProvisioned(volume)) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_GLUSTER_VOLUME_IS_NOT_THINLY_PROVISIONED); } if (glusterVolumeSnapshotDao.getByName(getGlusterVolumeId(), snapshot.getSnapshotName()) != null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_SNAPSHOT_ALREADY_EXISTS); } List<GlusterBrickEntity> bricks = volume.getBricks(); for (GlusterBrickEntity brick : bricks) { if (!brick.isOnline()) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_ONE_OR_MORE_BRICKS_ARE_DOWN); } } for (GlusterGeoRepSession session : georepSessions) { if (session.getSlaveNodeUuid() == null || session.getSlaveVolumeId() == null) { // Slave cluster is not maintained by engine, so cannot pause geo-rep session and create snapshot for // the volume return failValidation(EngineMessage.ACTION_TYPE_FAILED_REMOTE_CLUSTER_NOT_MAINTAINED_BY_ENGINE); } } return true; } @Override public AuditLogType getAuditLogTypeValue() { if (getSucceeded()) { return AuditLogType.GLUSTER_VOLUME_SNAPSHOT_CREATED; } else { return errorType == null ? AuditLogType.GLUSTER_VOLUME_SNAPSHOT_CREATE_FAILED : errorType; } } protected EngineLock acquireGeoRepSessionLock(Guid id) { EngineLock lock = new EngineLock(Collections.singletonMap(id.toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.GLUSTER_GEOREP, EngineMessage.ACTION_TYPE_FAILED_GEOREP_SESSION_LOCKED)), null); lockManager.acquireLockWait(lock); return lock; } }