package org.ovirt.engine.core.bll.storage.connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.ovirt.engine.core.bll.Backend;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.common.action.ConnectHostToStoragePoolServersParameters;
import org.ovirt.engine.core.common.action.HostStoragePoolParametersBase;
import org.ovirt.engine.core.common.action.StorageDomainParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatic;
import org.ovirt.engine.core.common.businessentities.StorageServerConnections;
import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface;
import org.ovirt.engine.core.common.businessentities.storage.LUNStorageServerConnectionMap;
import org.ovirt.engine.core.common.businessentities.storage.LUNs;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
import org.ovirt.engine.core.common.errors.EngineFault;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.StorageServerConnectionManagementVDSParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ISCSIStorageHelper extends StorageHelperBase {
private static final Logger log = LoggerFactory.getLogger(ISCSIStorageHelper.class);
@Override
protected Pair<Boolean, EngineFault> runConnectionStorageToDomain(StorageDomain storageDomain, Guid vdsId, int type) {
return runConnectionStorageToDomain(storageDomain, vdsId, type, null, Guid.Empty);
}
@SuppressWarnings("unchecked")
@Override
protected Pair<Boolean, EngineFault> runConnectionStorageToDomain(StorageDomain storageDomain,
Guid vdsId,
int type,
LUNs lun,
Guid storagePoolId) {
boolean isSuccess = true;
VDSReturnValue returnValue = null;
List<StorageServerConnections> list =
(lun == null) ? DbFacade.getInstance()
.getStorageServerConnectionDao().getAllForVolumeGroup(storageDomain.getStorage())
: lun.getLunConnections();
if (list.size() != 0) {
if (VDSCommandType.forValue(type) == VDSCommandType.DisconnectStorageServer) {
list = filterConnectionsUsedByOthers(list, storageDomain.getStorage(), lun != null ? lun.getLUNId()
: "");
} else if (VDSCommandType.forValue(type) == VDSCommandType.ConnectStorageServer) {
list = updateIfaces(list, vdsId);
}
Guid poolId = storagePoolId;
if (storageDomain != null && storageDomain.getStoragePoolId() != null) {
poolId = storageDomain.getStoragePoolId();
}
returnValue = Backend
.getInstance()
.getResourceManager()
.runVdsCommand(
VDSCommandType.forValue(type),
new StorageServerConnectionManagementVDSParameters(vdsId,
poolId, StorageType.ISCSI, list));
isSuccess = returnValue.getSucceeded();
if (isSuccess && VDSCommandType.forValue(type) == VDSCommandType.ConnectStorageServer) {
isSuccess = isConnectSucceeded((Map<String, String>) returnValue.getReturnValue(), list);
}
}
EngineFault engineFault = null;
if (!isSuccess && returnValue.getVdsError() != null) {
engineFault = new EngineFault();
engineFault.setError(returnValue.getVdsError().getCode());
}
return new Pair<>(isSuccess, engineFault);
}
public static List<StorageServerConnections> updateIfaces(List<StorageServerConnections> conns, Guid vdsId) {
List<StorageServerConnections> res = new ArrayList<>(conns);
for (StorageServerConnections conn : conns) {
// Get list of endpoints (nics or vlans) that will initiate iscsi sessions.
// Targets are represented by StorageServerConnections object (connection, iqn, port, portal).
List<VdsNetworkInterface> ifaces = DbFacade.getInstance().getInterfaceDao()
.getIscsiIfacesByHostIdAndStorageTargetId(vdsId, conn.getId());
if (!ifaces.isEmpty()) {
VdsNetworkInterface removedInterface = ifaces.remove(0);
setInterfaceProperties(conn, removedInterface);
// Iscsi target is represented by connection object, therefore if this target is approachable
// from more than one endpoint(initiator) we have to clone this connection per endpoint.
for (VdsNetworkInterface iface : ifaces) {
StorageServerConnections newConn = StorageServerConnections.copyOf(conn);
newConn.setId(Guid.newGuid().toString());
setInterfaceProperties(newConn, iface);
res.add(newConn);
}
}
}
return res;
}
@Override
public boolean prepareConnectHostToStoragePoolServers(CommandContext cmdContext,
ConnectHostToStoragePoolServersParameters parameters,
List<StorageServerConnections> connections) {
return prepareStorageServer(parameters, connections);
}
@Override
public void prepareDisconnectHostFromStoragePoolServers(HostStoragePoolParametersBase parameters,
List<StorageServerConnections> connections) {
prepareStorageServer(parameters, connections);
}
private boolean prepareStorageServer(HostStoragePoolParametersBase parameters,
List<StorageServerConnections> connections) {
List<StorageServerConnections> res = updateIfaces(connections, parameters.getVds().getId());
connections.clear();
connections.addAll(res);
return true;
}
private static void setInterfaceProperties(StorageServerConnections conn, VdsNetworkInterface iface) {
conn.setIface(iface.getName());
conn.setNetIfaceName(iface.isBridged() ? iface.getNetworkName() : iface.getName());
}
@SuppressWarnings("unchecked")
@Override
protected List<StorageServerConnections> filterConnectionsUsedByOthers(
List<StorageServerConnections> connections, String vgId, final String lunId) {
// if we have lun id then filter by this lun
// else get vg's luns from db
List<String> lunsByVg =
lunId.isEmpty() ? DbFacade.getInstance()
.getLunDao()
.getAllForVolumeGroup(vgId)
.stream()
.map(LUNs::getLUNId)
.collect(Collectors.toList()) : null;
// if a luns were retrieved by vgId, they can belongs not only to storage but also to disks
// at that case they should left at db
List<String> lunsByVgWithNoDisks = new ArrayList<>();
if (lunId.isEmpty()) {
for (String lunIdByVg : lunsByVg) {
if (DbFacade.getInstance().getDiskLunMapDao().getDiskIdByLunId(lunIdByVg) == null) {
lunsByVgWithNoDisks.add(lunIdByVg);
}
}
} else {
lunsByVgWithNoDisks.add(lunId);
}
List<StorageServerConnections> toRemove = new ArrayList<>();
for (StorageServerConnections connection : connections) {
fillConnectionDetailsIfNeeded(connection);
if (connection.getId() != null) {
List<String> list = DbFacade.getInstance()
.getLunDao()
.getAllForStorageServerConnection(connection.getId())
.stream()
.map(LUNs::getLUNId)
.collect(Collectors.toList());
if (0 < CollectionUtils.subtract(list, lunsByVgWithNoDisks).size()) {
toRemove.add(connection);
}
}
}
return (List<StorageServerConnections>) CollectionUtils.subtract(connections, toRemove);
}
public static StorageServerConnections findConnectionWithSameDetails(StorageServerConnections connection) {
// As we encrypt the password when saving the connection to the DB and each encryption generates different
// result,
// we can't query the connections to check if connection with the exact
// same details was already added - so we query the connections with the same (currently relevant) details and
// then compare the password after it was already
// decrypted.
// NOTE- THIS METHOD IS CURRENTLY USED ALSO FOR FCP connections, change with care.
List<StorageServerConnections> connections =
DbFacade.getInstance().getStorageServerConnectionDao().getAllForConnection(connection);
for (StorageServerConnections dbConnection : connections) {
if (Objects.equals(dbConnection.getPassword(), connection.getPassword())) {
return dbConnection;
}
}
return null;
}
private void fillConnectionDetailsIfNeeded(StorageServerConnections connection) {
// in case that the connection id is null (in case it wasn't loaded from the db before) - we can attempt to load
// it from the db by its details.
if (connection.getId() == null) {
StorageServerConnections dbConnection = findConnectionWithSameDetails(connection);
if (dbConnection != null) {
connection.setId(dbConnection.getId());
}
}
}
@Override
public boolean isConnectSucceeded(final Map<String, String> returnValue,
List<StorageServerConnections> connections) {
boolean result = true;
List<String> failedConnectionsList =
returnValue.keySet().stream().filter(a -> !"0".equals(returnValue.get(a))).collect(Collectors.toList());
for (String failedConnection : failedConnectionsList) {
List<LUNs> failedLuns = DbFacade.getInstance().getLunDao()
.getAllForStorageServerConnection(failedConnection);
if (!failedLuns.isEmpty()) {
for (LUNs lun : failedLuns) {
// TODO: check if LUNs in the same pool.
List<String> strings =
DbFacade.getInstance()
.getStorageServerConnectionLunMapDao()
.getAll(lun.getLUNId())
.stream()
.map(LUNStorageServerConnectionMap::getStorageServerConnection)
.collect(Collectors.toList());
if (CollectionUtils.subtract(strings, failedConnectionsList).size() == 0) {
// At case of failure the appropriate log message will be
// added
log.info("The lun with id '{}' was reported as problematic", lun.getPhysicalVolumeId());
for (String connectionFailed : failedConnectionsList) {
String connectionField =
addToAuditLogErrorMessage(connectionFailed,
returnValue.get(connectionFailed),
connections, lun);
printLog(log, connectionField, returnValue.get(connectionFailed));
}
return false;
}
}
} else {
result = false;
printLog(log, failedConnection, returnValue.get(failedConnection));
}
}
return result;
}
@Override
public boolean storageDomainRemoved(StorageDomainStatic storageDomain) {
List<StorageServerConnections> list = DbFacade.getInstance()
.getStorageServerConnectionDao().getAllForVolumeGroup(storageDomain.getStorage());
for (StorageServerConnections connection : filterConnectionsUsedByOthers(list, storageDomain.getStorage(), "")) {
DbFacade.getInstance().getStorageServerConnectionDao().remove(connection.getId());
}
// There is no need to remove entries from lun_storage_server_connection_map,
// as the foreign key from the luns table is defined as ON DELETE CASCADE.
removeStorageDomainLuns(storageDomain);
return true;
}
@Override
public boolean connectStorageToDomainByVdsId(StorageDomain storageDomain, Guid vdsId) {
return runConnectionStorageToDomain(storageDomain, vdsId, VDSCommandType.ConnectStorageServer.getValue()).getFirst();
}
@Override
public Pair<Boolean, EngineFault> connectStorageToDomainByVdsIdDetails(StorageDomain storageDomain, Guid vdsId) {
return runConnectionStorageToDomain(storageDomain, vdsId, VDSCommandType.ConnectStorageServer.getValue());
}
@Override
public boolean disconnectStorageFromDomainByVdsId(StorageDomain storageDomain, Guid vdsId) {
return runConnectionStorageToDomain(storageDomain, vdsId, VDSCommandType.DisconnectStorageServer.getValue()).getFirst();
}
@Override
public boolean connectStorageToLunByVdsId(StorageDomain storageDomain, Guid vdsId, LUNs lun, Guid storagePoolId) {
return runConnectionStorageToDomain(storageDomain,
vdsId,
VDSCommandType.ConnectStorageServer.getValue(),
lun,
storagePoolId).getFirst();
}
@Override
public boolean disconnectStorageFromLunByVdsId(StorageDomain storageDomain, Guid vdsId, LUNs lun) {
return runConnectionStorageToDomain(storageDomain, vdsId, VDSCommandType.DisconnectStorageServer.getValue(),
lun, Guid.Empty).getFirst();
}
@Override
public boolean syncDomainInfo(StorageDomain storageDomain, Guid vdsId) {
// Synchronize LUN details comprising the storage domain with the DB
StorageDomainParametersBase parameters = new StorageDomainParametersBase(storageDomain.getId());
parameters.setVdsId(vdsId);
return Backend.getInstance()
.runInternalAction(VdcActionType.SyncLunsInfoForBlockStorageDomain, parameters)
.getSucceeded();
}
}