package org.ovirt.engine.core.bll.storage.connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.storage.domain.StorageDomainCommandBase;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.ExtendSANStorageDomainParameters;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VdsSpmStatus;
import org.ovirt.engine.core.common.businessentities.storage.LUNs;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.GetDeviceListVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.GetDevicesVisibilityVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.LunDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("serial")
@NonTransactiveCommandAttribute
public class ConnectAllHostsToLunCommand<T extends ExtendSANStorageDomainParameters> extends
StorageDomainCommandBase<T> {
private static final Logger log = LoggerFactory.getLogger(ConnectAllHostsToLunCommand.class);
@Inject
private LunDao lunDao;
public ConnectAllHostsToLunCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
public static class ConnectAllHostsToLunCommandReturnValue extends VdcReturnValueBase {
private VDS failedVds;
private LUNs failedLun;
public VDS getFailedVds() {
return failedVds;
}
public void setFailedVds(VDS failedVds) {
this.failedVds = failedVds;
}
public LUNs getFailedLun() {
return failedLun;
}
public void setFailedLun(LUNs failedLun) {
this.failedLun = failedLun;
}
}
@Override
protected ConnectAllHostsToLunCommandReturnValue createReturnValue() {
return new ConnectAllHostsToLunCommandReturnValue();
}
private ConnectAllHostsToLunCommandReturnValue getResult() {
return (ConnectAllHostsToLunCommandReturnValue) getReturnValue();
}
@Override
protected void executeCommand() {
VDS spmVds = getAllRunningVdssInPool().stream()
.filter(vds -> vds.getSpmStatus() == VdsSpmStatus.SPM).findFirst().orElse(null);
final List<LUNs> luns = getHostLuns(spmVds);
final Map<String, LUNs> lunsMap = new HashMap<>();
for (LUNs lun : luns) {
lunsMap.put(lun.getLUNId(), lun);
}
final List<LUNs> processedLunsList = new ArrayList<>();
for (String lunId : getParameters().getLunIds()) {
LUNs lun = lunsMap.get(lunId);
if (lun == null) {
//fail
handleFailure(spmVds, lunDao.get(lunId));
return;
}
lun.setVolumeGroupId(getStorageDomain().getStorage());
processedLunsList.add(lun);
}
// connect all vds in pool (except spm) to lun and getDeviceList
Pair<Boolean, Map<String, List<Guid>>> result = connectVdsToLun(processedLunsList);
if (result.getFirst()) {
getReturnValue().setActionReturnValue(processedLunsList);
setCommandShouldBeLogged(false);
setSucceeded(true);
} else {
// disconnect all hosts if connection is not in use by other luns
Map<String, List<Guid>> processed = result.getSecond();
for (Map.Entry<String, List<Guid>> entry : processed.entrySet()) {
for (Guid vdsId : entry.getValue()) {
LUNs lun = lunsMap.get(entry.getKey());
StorageHelperDirector.getInstance().getItem(getStorageDomain().getStorageType())
.disconnectStorageFromLunByVdsId(getStorageDomain(), vdsId, lun);
}
}
}
}
/**
* The following method will connect all provided lund to all running host in pool
*
* @param luns
* - the luns which should be connected
* @return the map where the key is true/false value which means if connection successes/not successes and value is
* map of luns Ids -> connected hosts
*/
private Pair<Boolean, Map<String, List<Guid>>> connectVdsToLun(List<LUNs> luns) {
Map<String, List<Guid>> resultMap = new HashMap<>();
for (VDS vds : getAllRunningVdssInPool()) {
// try to connect vds to luns and getDeviceList in order to refresh them
for (LUNs lun : luns) {
if (!connectStorageToLunByVdsId(vds, lun)) {
log.error("Could not connect host '{}' to lun '{}'", vds.getName(), lun.getLUNId());
setVds(vds);
handleFailure(vds, lun);
return new Pair<>(Boolean.FALSE, resultMap);
} else {
List<Guid> hosts = resultMap.get(lun.getLUNId());
if (hosts == null) {
hosts = new ArrayList<>();
resultMap.put(lun.getLUNId(), hosts);
}
hosts.add(vds.getId());
}
}
// Refresh all connected luns to host
if (!validateConnectedLuns(vds, getParameters().getLunIds())) {
return new Pair<>(Boolean.FALSE, resultMap);
}
}
return new Pair<>(Boolean.TRUE, resultMap);
}
private boolean connectStorageToLunByVdsId(VDS vds, LUNs lun) {
try {
return StorageHelperDirector.getInstance()
.getItem(getStorageDomain().getStorageType())
.connectStorageToLunByVdsId(getStorageDomain(), vds.getId(), lun, Guid.Empty);
} catch (EngineException e) {
handleFailure(vds, lun);
throw e;
}
}
@SuppressWarnings("unchecked")
private List<LUNs> getHostLuns(VDS vds) {
try {
return (List<LUNs>) runVdsCommand(
VDSCommandType.GetDeviceList,
new GetDeviceListVDSCommandParameters(vds.getId(),
getStorageDomain().getStorageType())).getReturnValue();
} catch (EngineException e) {
getResult().setFailedVds(vds);
throw e;
}
}
/**
* Verify that all luns are connected to the host.
*
* @param vds
* - the host
* @param processedLunIds
* - luns ids which we wants to check
* @return - true if all luns are connected to the host, false otherwise
*
* @throws EngineException If the underlying call to VDSM fails
*/
private boolean validateConnectedLuns(VDS vds, List<String> processedLunIds) {
Map<String, Boolean> res;
try {
res = (Map<String, Boolean>) runVdsCommand(VDSCommandType.GetDevicesVisibility,
new GetDevicesVisibilityVDSCommandParameters(vds.getId(),
processedLunIds.toArray(new String[processedLunIds.size()]))).getReturnValue();
} catch(EngineException e) {
handleFailure(vds, null);
throw e;
}
for (Map.Entry<String, Boolean> deviceVisibility : res.entrySet()) {
if (!Boolean.TRUE.equals(deviceVisibility.getValue())) {
handleFailure(vds, lunDao.get(deviceVisibility.getKey()));
return false;
}
}
return true;
}
private void handleFailure(VDS vds, LUNs lun) {
ConnectAllHostsToLunCommandReturnValue result = getResult();
result.setFailedVds(vds);
result.setFailedLun(lun);
}
@Override
public AuditLogType getAuditLogTypeValue() {
// this should return only error, if command succeeded no logging is
// required
setVds(getResult().getFailedVds()); // For audit logging purposes in case of an error
return AuditLogType.USER_CONNECT_HOSTS_TO_LUN_FAILED;
}
}