package org.ovirt.engine.core.bll.storage.domain;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.collections.CollectionUtils;
import org.ovirt.engine.core.bll.LockMessagesMatchUtil;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
import org.ovirt.engine.core.bll.SerialChildCommandsExecutionCallback;
import org.ovirt.engine.core.bll.SerialChildExecutingCommand;
import org.ovirt.engine.core.bll.ValidationResult;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.connection.StorageHelperDirector;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.validator.storage.BlockStorageDomainValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.ReduceSANStorageDomainDevicesCommandParameters;
import org.ovirt.engine.core.common.action.RemoveDeviceFromSANStorageDomainCommandParameters;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.StorageFormatType;
import org.ovirt.engine.core.common.businessentities.storage.LUNs;
import org.ovirt.engine.core.common.errors.EngineError;
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.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.dao.LunDao;
import org.ovirt.engine.core.dao.StorageDomainStaticDao;
@NonTransactiveCommandAttribute
public class ReduceSANStorageDomainDevicesCommand<T extends ReduceSANStorageDomainDevicesCommandParameters> extends StorageDomainCommandBase<T> implements SerialChildExecutingCommand {
@Inject
private AuditLogDirector auditLogDirector;
@Inject
private BlockStorageDomainValidator blockSDValidator;
@Inject
private BlockStorageDomainHelper blockStorageDomainHelper;
@Inject
private LunDao lunDao;
@Inject
private StorageDomainStaticDao storageDomainStaticDao;
public ReduceSANStorageDomainDevicesCommand(T parameters, CommandContext cmdContext) {
super(parameters, cmdContext);
setVdsId(getParameters().getVdsId());
}
@Override
protected void executeCommand() {
// TODO: The domain is currently being locked only in memory, that's because LOCKED domains are considered
// as active in respect to the pool metadata and we don't want host to start monitor that domain while
// its edited and on maintenance status from the engine perspective.
setSucceeded(true);
}
private void connectHostToDomain() {
if (!StorageHelperDirector.getInstance()
.getItem(getStorageDomain().getStorageType())
.connectStorageToDomainByVdsId(getStorageDomain(), getVdsId())) {
throw new EngineException(EngineError.StorageServerConnectionError);
}
}
private void disconnectHostFromDomain() {
StorageHelperDirector.getInstance()
.getItem(getStorageDomain().getStorageType())
.disconnectStorageFromDomainByVdsId(getStorageDomain(), getVdsId());
}
private void prepareForRemove() {
List<String> devices = lunDao.getAllForVolumeGroup(getStorageDomain().getStorage())
.stream()
.map(LUNs::getId)
.filter(x -> !getParameters().getDevicesToReduce().contains(x))
.collect(toList());
getParameters().setDstDevices(devices);
persistCommandIfNeeded();
}
// Includes validations that involve storage access and shouldn't be performed on the command synchronous part.
private void validateRemove() {
// Performed here in order to complete the information/perform validation for domains for which we don't have
// the metadata device information (as we didn't have that information prior to v4.1). We don't want to perform
// vdsm call in the synchronous validate() to get that information, therefore it's perform as part of the
// asynchronous execution.
if (getStorageDomain().getVgMetadataDevice() == null || getStorageDomain().getFirstMetadataDevice() == null) {
blockStorageDomainHelper.fillMetadataDevicesInfo(getStorageDomain().getStorageStaticData(),
getParameters().getVdsId());
storageDomainStaticDao.update(getStorageDomain().getStorageStaticData());
List<String> metadataDevices = blockStorageDomainHelper.findMetadataDevices(getStorageDomain(),
getParameters().getDevicesToReduce());
if (!metadataDevices.isEmpty()) {
setCustomCommaSeparatedValues("deviceIds", metadataDevices);
auditLogDirector.log(this, AuditLogType.USER_REDUCE_DOMAIN_DEVICES_FAILED_METADATA_DEVICES);
throw new EngineException(EngineError.GeneralException, "Cannot perform on metadata devices");
}
}
// Performed here in order to avoid storage access during the validate() execution.
validateFreeSpace();
}
public void validateFreeSpace() {
List<LUNs> allLuns =
blockStorageDomainHelper.getVgLUNsInfo(getStorageDomain().getStorageStaticData(), getVdsId());
if (allLuns == null) {
auditLogDirector.log(this, AuditLogType.USER_REDUCE_DOMAIN_DEVICES_FAILED_TO_GET_DOMAIN_INFO);
throw new EngineException(EngineError.GeneralException, "Failed to get the vg info");
}
long freeExtents = allLuns.stream()
.filter(l -> getParameters().getDstDevices().contains(l.getLUNId()))
.mapToLong(l -> l.getPeCount() - l.getPeAllocatedCount())
.sum();
long neededExtents = allLuns.stream()
.filter(l -> getParameters().getDevicesToReduce().contains(l.getLUNId()))
.mapToLong(LUNs::getPeAllocatedCount)
.sum();
if (neededExtents > freeExtents) {
auditLogDirector.log(this, AuditLogType.USER_REDUCE_DOMAIN_DEVICES_FAILED_NO_FREE_SPACE);
throw new EngineException(EngineError.GeneralException, "Not enough free space on the destination devices");
}
}
@Override
public boolean performNextOperation(int completedChildCount) {
if (getParameters().getRemoveIndex() == 0) {
prepareForRemove();
connectHostToDomain();
validateRemove();
}
if (getParameters().getRemoveIndex() < getParameters().getDevicesToReduce().size()) {
runInternalActionWithTasksContext(VdcActionType.RemoveDeviceFromSANStorageDomain,
createRemoveParameters(getParameters().getDevicesToReduce().get(getParameters().getRemoveIndex())));
getParameters().setRemoveIndex(getParameters().getRemoveIndex() + 1);
persistCommandIfNeeded();
return true;
}
return false;
}
private RemoveDeviceFromSANStorageDomainCommandParameters createRemoveParameters(String deviceId) {
RemoveDeviceFromSANStorageDomainCommandParameters p =
new RemoveDeviceFromSANStorageDomainCommandParameters(getParameters().getStorageDomainId(), deviceId);
p.setEndProcedure(EndProcedure.PARENT_MANAGED);
p.setVdsId(getParameters().getVdsId());
p.setParentCommand(getActionType());
p.setParentParameters(getParameters());
p.setDestinationDevices(getParameters().getDstDevices());
return p;
}
@Override
public CommandCallback getCallback() {
return new SerialChildCommandsExecutionCallback();
}
@Override
protected boolean validate() {
if (CollectionUtils.isEmpty(getParameters().getDevicesToReduce())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_NO_DEVICES_PROVIDED);
}
if (!checkStorageDomain()) {
return false;
}
if (!getStorageDomain().getStorageType().isBlockDomain()) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_TYPE_ILLEGAL);
}
if (!validateReduceDeviceSupported()) {
return false;
}
if (!validateDevices()) {
return false;
}
if (!checkStorageDomainStatus(StorageDomainStatus.Maintenance)) {
return false;
}
if (!initializeVds()) {
return false;
}
return true;
}
@Override
protected boolean initializeVds() {
if (super.initializeVds()) {
getParameters().setVdsId(getVds().getId());
persistCommandIfNeeded();
return true;
}
return false;
}
private boolean validateReduceDeviceSupported() {
if (getStorageDomain().getStorageFormat() == StorageFormatType.V1) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_FORMAT_ILLEGAL,
String.format("$storageFormat %1$s", StorageFormatType.V1.toString()));
}
if (getStoragePool() != null
&& !FeatureSupported.reduceDeviceFromStorageDomain(getStoragePool().getCompatibilityVersion())) {
return failValidation(EngineMessage.ACTION_TYPE_FAILED_REDUCE_DOMAIN_DEVICE_NOT_SUPPORTED);
}
return true;
}
@Override
public AuditLogType getAuditLogTypeValue() {
switch (getActionState()) {
case EXECUTE:
return AuditLogType.USER_REDUCE_DOMAIN_DEVICES_STARTED;
case END_FAILURE:
return AuditLogType.USER_REDUCE_DOMAIN_DEVICES_FAILED;
case END_SUCCESS:
return getSucceeded() ? AuditLogType.USER_REDUCE_DOMAIN_DEVICES_SUCCEEDED
: AuditLogType.USER_REDUCE_DOMAIN_DEVICES_FAILED;
}
return null;
}
@Override
protected Map<String, Pair<String, String>> getExclusiveLocks() {
if (getParameters().getStorageDomainId() != null) {
return Collections.singletonMap(getParameters().getStorageDomainId().toString(),
LockMessagesMatchUtil.makeLockingPair(LockingGroup.STORAGE,
EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_IS_BEING_REDUCED));
}
return super.getExclusiveLocks();
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
private boolean validateDevices() {
Set<String> devices = getParameters().getDevicesToReduce().stream().collect(toSet());
if (devices.size() != getParameters().getDevicesToReduce().size()) {
return validate(new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_DUPLICATE_DEVICE));
}
return validate(blockSDValidator.lunsInDomain(getStorageDomain(), devices)) &&
validate(blockSDValidator.lunsEligibleForOperation(getStorageDomain(), devices));
}
protected void endActionOnDevices() {
for (VdcActionParametersBase p : getParameters().getImagesParameters()) {
getBackend().endAction(p.getCommandType(),
p,
getContext().clone().withoutCompensationContext().withoutExecutionContext().withoutLock());
}
}
private void endOperation() {
try {
disconnectHostFromDomain();
} catch (Exception e) {
log.error("Failed to disconnect the host from the domain storage servers, ignoring", e);
}
// TODO: currently we need to execute the endAction() of the child commands from here after we disconnected from
// the domain, that's because the connections filter code in the StorageHelper that currently assumes that if
// lun id is passed there's no lun disk using the device. After that will be fixed we may let the child commands
// end by themselves (EndProcedure = COMMAND_MANAGED) and remove the device from the domain/disconnect on that
// phase before all the devices were reduced from the domain.
endActionOnDevices();
setSucceeded(true);
}
@Override
protected void setActionMessageParameters() {
addValidationMessage(EngineMessage.VAR__ACTION__UPDATE);
addValidationMessage(EngineMessage.VAR__TYPE__STORAGE__DOMAIN);
}
@Override
protected void endSuccessfully() {
endOperation();
}
@Override
protected void endWithFailure() {
endOperation();
}
}