package org.ovirt.engine.core.bll.gluster; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.job.ExecutionContext; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.action.gluster.CreateGlusterVolumeParameters; import org.ovirt.engine.core.common.action.gluster.GlusterVolumeOptionParameters; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.gluster.AccessProtocol; import org.ovirt.engine.core.common.businessentities.gluster.GlusterBrickEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeOptionEntity; import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeType; import org.ovirt.engine.core.common.businessentities.gluster.TransportType; import org.ovirt.engine.core.common.constants.gluster.GlusterConstants; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.gluster.GlusterFeatureSupported; import org.ovirt.engine.core.common.job.Step; import org.ovirt.engine.core.common.job.StepEnum; import org.ovirt.engine.core.common.validation.group.CreateEntity; import org.ovirt.engine.core.common.validation.group.gluster.CreateReplicatedVolume; import org.ovirt.engine.core.common.validation.group.gluster.CreateStripedVolume; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.gluster.CreateGlusterVolumeVDSParameters; /** * BLL command to create a new Gluster Volume */ @NonTransactiveCommandAttribute public class CreateGlusterVolumeCommand extends GlusterCommandBase<CreateGlusterVolumeParameters> { public CreateGlusterVolumeCommand(CreateGlusterVolumeParameters params, CommandContext commandContext) { super(params, commandContext); if (getVolume() != null) { setClusterId(getVolume().getClusterId()); } } private GlusterVolumeEntity getVolume() { return getParameters().getVolume(); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution).withWait(true); } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = super.getJobMessageProperties(); if (getVolume() != null) { jobProperties.put(GlusterConstants.VOLUME, getVolume().getName()); } } return jobProperties; } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__CREATE); addValidationMessage(EngineMessage.VAR__TYPE__GLUSTER_VOLUME); } @Override protected List<Class<?>> getValidationGroups() { addValidationGroup(CreateEntity.class); if (getVolume().getVolumeType().isReplicatedType()) { addValidationGroup(CreateReplicatedVolume.class); } if (getVolume().getVolumeType().isStripedType()) { addValidationGroup(CreateStripedVolume.class); } return super.getValidationGroups(); } @Override protected boolean validate() { if (!super.validate()) { return false; } Cluster cluster = getCluster(); if (cluster == null) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_CLUSTER_IS_NOT_VALID); return false; } if (!cluster.supportsGlusterService()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_CLUSTER_DOES_NOT_SUPPORT_GLUSTER); return false; } if (getVolume().getVolumeType().isDispersedType()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_CREATION_OF_DISPERSE_VOLUME_NOT_SUPPORTED); return false; } if (volumeNameExists(getVolume().getName())) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_GLUSTER_VOLUME_NAME_ALREADY_EXISTS); addValidationMessageVariable("volumeName", getVolume().getName()); return false; } if (getVolume().getIsArbiter()){ if(!GlusterFeatureSupported.glusterArbiterVolumeSupported(getCluster().getCompatibilityVersion())){ return failValidation(EngineMessage.ACTION_TYPE_FAILED_GLUSTER_ARBITER_VOLUME_NOT_SUPPORTED); } if(!getVolume().getVolumeType().isReplicatedType() || getVolume().getReplicaCount() != 3){ return failValidation(EngineMessage.ACTION_TYPE_FAILED_GLUSTER_ARBITER_VOLUME_SHOULD_BE_REPLICA_3_VOLUME); } } return validateBricks(getVolume()); } private boolean volumeNameExists(String volumeName) { return glusterVolumeDao.getByName(getClusterId(), volumeName) != null; } /* * (non-Javadoc) * * @see org.ovirt.engine.core.bll.CommandBase#executeCommand() */ @Override protected void executeCommand() { // set the gluster volume name for audit purpose setGlusterVolumeName(getVolume().getName()); if(getVolume().getTransportTypes() == null || getVolume().getTransportTypes().isEmpty()) { getVolume().addTransportType(TransportType.TCP); } // GLUSTER access protocol is enabled by default getVolume().addAccessProtocol(AccessProtocol.GLUSTER); if (!getVolume().getAccessProtocols().contains(AccessProtocol.NFS)) { getVolume().disableNFS(); } if (getVolume().getAccessProtocols().contains(AccessProtocol.CIFS)) { getVolume().enableCifs(); } VDSReturnValue returnValue = runVdsCommand( VDSCommandType.CreateGlusterVolume, new CreateGlusterVolumeVDSParameters(upServer.getId(), getVolume(), upServer.getClusterCompatibilityVersion(), getParameters().isForce())); setSucceeded(returnValue.getSucceeded()); if(!getSucceeded()) { handleVdsError(AuditLogType.GLUSTER_VOLUME_CREATE_FAILED, returnValue.getVdsError().getMessage()); return; } // Volume created successfully. Insert it to database. GlusterVolumeEntity createdVolume = (GlusterVolumeEntity) returnValue.getReturnValue(); setVolumeType(createdVolume); setBrickOrder(createdVolume.getBricks()); if(createdVolume.getIsArbiter()){ setArbiterFlag(createdVolume); } addVolumeToDb(createdVolume); // If we log successful volume creation at the end of this command, // the messages from SetGlusterVolumeOptionCommand appear first, // making it look like options were set before volume was created. // Hence we explicitly log the volume creation before setting the options. auditLogDirector.log(this, AuditLogType.GLUSTER_VOLUME_CREATE); // And don't log it at the end setCommandShouldBeLogged(false); // set all options of the volume setVolumeOptions(createdVolume); getReturnValue().setActionReturnValue(createdVolume.getId()); } /** * Sets every third brick as arbiter brick if GlusterVolume is an arbiter volume */ private void setArbiterFlag(GlusterVolumeEntity volume) { for(int i=2;i<volume.getBricks().size();i+=3){ volume.getBricks().get(i).setIsArbiter(volume.getIsArbiter()); } } private void setVolumeType(GlusterVolumeEntity createdVolume) { if (createdVolume.getVolumeType() == GlusterVolumeType.REPLICATE && createdVolume.getBricks().size() > createdVolume.getReplicaCount()) { createdVolume.setVolumeType(GlusterVolumeType.DISTRIBUTED_REPLICATE); } else if (createdVolume.getVolumeType() == GlusterVolumeType.DISTRIBUTED_REPLICATE && createdVolume.getBricks().size() == createdVolume.getReplicaCount()) { createdVolume.setVolumeType(GlusterVolumeType.REPLICATE); } else if (createdVolume.getVolumeType() == GlusterVolumeType.STRIPE && createdVolume.getBricks().size() > createdVolume.getStripeCount()) { createdVolume.setVolumeType(GlusterVolumeType.DISTRIBUTED_STRIPE); } else if (createdVolume.getVolumeType() == GlusterVolumeType.DISTRIBUTED_STRIPE && createdVolume.getBricks().size() == createdVolume.getStripeCount()) { createdVolume.setVolumeType(GlusterVolumeType.STRIPE); } else if (createdVolume.getVolumeType() == GlusterVolumeType.STRIPED_REPLICATE && createdVolume.getBricks().size() > createdVolume.getReplicaCount() * createdVolume.getStripeCount()) { createdVolume.setVolumeType(GlusterVolumeType.DISTRIBUTED_STRIPED_REPLICATE); } else if (createdVolume.getVolumeType() == GlusterVolumeType.DISTRIBUTED_STRIPED_REPLICATE && createdVolume.getBricks().size() == createdVolume.getReplicaCount() * createdVolume.getStripeCount()) { createdVolume.setVolumeType(GlusterVolumeType.STRIPED_REPLICATE); } } /** * Sets all options of a volume by invoking the action {@link VdcActionType#SetGlusterVolumeOption} in a loop. <br> * Errors if any are collected and added to "executeFailedMessages" */ private void setVolumeOptions(GlusterVolumeEntity volume) { List<String> errors = new ArrayList<>(); for (GlusterVolumeOptionEntity option : volume.getOptions()) { // make sure that volume id is set option.setVolumeId(volume.getId()); VdcReturnValueBase setOptionReturnValue = runInternalAction( VdcActionType.SetGlusterVolumeOption, new GlusterVolumeOptionParameters(option), createCommandContext(volume, option)); if (!setOptionReturnValue.getSucceeded()) { setSucceeded(false); errors.addAll(setOptionReturnValue.getValidationMessages()); errors.addAll(setOptionReturnValue.getExecuteFailedMessages()); } } if (!errors.isEmpty()) { handleVdsErrors(AuditLogType.GLUSTER_VOLUME_OPTION_SET_FAILED, errors); } } /** * Creates command context for setting a given option on the given volume */ private CommandContext createCommandContext(GlusterVolumeEntity volume, GlusterVolumeOptionEntity option) { // Add sub-step for setting given option Step setOptionStep = addSubStep(StepEnum.EXECUTING, StepEnum.SETTING_GLUSTER_OPTION, getOptionValues(volume, option)); // Create execution context for setting option ExecutionContext setOptionCtx = new ExecutionContext(); setOptionCtx.setMonitored(true); setOptionCtx.setStep(setOptionStep); return cloneContext().withExecutionContext(setOptionCtx).withoutLock(); } private Map<String, String> getOptionValues(GlusterVolumeEntity volume, GlusterVolumeOptionEntity option) { Map<String, String> values = new HashMap<>(); values.put(GlusterConstants.CLUSTER, getClusterName()); values.put(GlusterConstants.VOLUME, volume.getName()); values.put(GlusterConstants.OPTION_KEY, option.getKey()); values.put(GlusterConstants.OPTION_VALUE, option.getValue()); return values; } /** * Validates the the number of bricks against the replica count or stripe count based on volume type */ private boolean validateBricks(GlusterVolumeEntity volume) { List<GlusterBrickEntity> bricks = volume.getBricks(); if (bricks.isEmpty()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_BRICKS_REQUIRED); return false; } int brickCount = bricks.size(); int replicaCount = volume.getReplicaCount(); int stripeCount = volume.getStripeCount(); if (volume.getVolumeType().isReplicatedType() && replicaCount < 2) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_REPLICA_COUNT_MIN_2); return false; } if (volume.getVolumeType().isStripedType() && stripeCount < 4) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_STRIPE_COUNT_MIN_4); return false; } switch (volume.getVolumeType()) { case REPLICATE: if (brickCount != replicaCount) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_REPLICATE); return false; } break; case DISTRIBUTED_REPLICATE: if (brickCount < replicaCount || Math.IEEEremainder(brickCount, replicaCount) != 0) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_DISTRIBUTED_REPLICATE); return false; } break; case STRIPE: if (brickCount != stripeCount) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_STRIPE); return false; } break; case DISTRIBUTED_STRIPE: if (brickCount <= stripeCount || Math.IEEEremainder(brickCount, stripeCount) != 0) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_DISTRIBUTED_STRIPE); return false; } break; case STRIPED_REPLICATE: if ( Math.IEEEremainder(brickCount, stripeCount * replicaCount) != 0) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_STRIPED_REPLICATE); return false; } break; case DISTRIBUTED_STRIPED_REPLICATE: if ( brickCount <= stripeCount * replicaCount || Math.IEEEremainder(brickCount, stripeCount * replicaCount) != 0) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_INVALID_BRICK_COUNT_FOR_DISTRIBUTED_STRIPED_REPLICATE); return false; } break; default: break; } boolean ret = updateBrickServerAndInterfaceNames(bricks, true) && validateDuplicateBricks(bricks); //only validate same server check for HC clusters. if (getCluster().supportsGlusterService() && getCluster().supportsVirtService()) { ret = ret && validateNotSameServer(bricks, replicaCount); } return ret; } private void setBrickOrder(List<GlusterBrickEntity> bricks) { for (int i = 0; i < bricks.size(); i++) { bricks.get(i).setBrickOrder(i); } } private void addVolumeToDb(final GlusterVolumeEntity createdVolume) { // volume fetched from VDSM doesn't contain cluster id as // GlusterFS is not aware of multiple clusters createdVolume.setClusterId(getClusterId()); glusterVolumeDao.save(createdVolume); } @Override public AuditLogType getAuditLogTypeValue() { if (!getSucceeded()) { // Success need not be logged at the end of execution, // as it is already logged by executeCommand() return errorType == null ? AuditLogType.GLUSTER_VOLUME_CREATE_FAILED : errorType; } return super.getAuditLogTypeValue(); } }