package org.ovirt.engine.core.vdsbroker.gluster;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.ovirt.engine.core.common.businessentities.VdsStatic;
import org.ovirt.engine.core.common.businessentities.gluster.GlusterBrickEntity;
import org.ovirt.engine.core.common.businessentities.gluster.GlusterServer;
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.TransportType;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dao.gluster.GlusterDBUtils;
import org.ovirt.engine.core.di.Injector;
import org.ovirt.engine.core.vdsbroker.irsbroker.StatusReturn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The return type to receive a list of gluster volumes. The constructor takes cluster id as well, so that
* correct host can be identified when populating the bricks of a volume
*/
public final class GlusterVolumesListReturn extends StatusReturn {
private static final String VOLUMES = "volumes";
private static final String VOLUME_NAME = "volumeName";
private static final String UUID = "uuid";
private static final String VOLUME_TYPE = "volumeType";
private static final String TRANSPORT_TYPE = "transportType";
private static final String VOLUME_STATUS = "volumeStatus";
private static final String BRICKS = "bricks";
private static final String OPTIONS = "options";
private static final String VOLUME_STATUS_ONLINE = "ONLINE";
private static final String REPLICA_COUNT = "replicaCount";
private static final String STRIPE_COUNT = "stripeCount";
private static final String DISPERSE_COUNT = "disperseCount";
private static final String REDUNDANCY_COUNT = "redundancyCount";
private static final String BRICKS_INFO = "bricksInfo"; //contains brick name and server uuid
private static final String IS_ARBITER = "isArbiter";
private static final String NAME = "name";
private static final String HOST_UUID = "hostUuid";
private static final Logger log = LoggerFactory.getLogger(GlusterVolumesListReturn.class);
private static final GlusterDBUtils dbUtils = Injector.get(GlusterDBUtils.class);
private Guid clusterId;
private final Map<Guid, GlusterVolumeEntity> volumes = new HashMap<>();
@SuppressWarnings("unchecked")
public GlusterVolumesListReturn(Guid clusterId, Map<String, Object> innerMap) {
super(innerMap);
this.clusterId = clusterId;
if(getStatus().code != 0) {
return;
}
Map<String, Object> volumesMap = (Map<String, Object>) innerMap.get(VOLUMES);
for (Entry<String, Object> entry : volumesMap.entrySet()) {
log.debug("received volume '{}'", entry.getKey());
GlusterVolumeEntity volume = getVolume((Map<String, Object>)entry.getValue());
volumes.put(volume.getId(), volume);
}
}
@SuppressWarnings({ "unchecked", "incomplete-switch" })
private GlusterVolumeEntity getVolume(Map<String, Object> map) {
GlusterVolumeEntity volume = new GlusterVolumeEntity();
volume.setClusterId(clusterId);
volume.setId(Guid.createGuidFromStringDefaultEmpty((String)map.get(UUID)));
volume.setName((String)map.get(VOLUME_NAME));
volume.setVolumeType((String)map.get(VOLUME_TYPE));
if (volume.getVolumeType() !=null) {
if (volume.getVolumeType().isReplicatedType()) {
volume.setReplicaCount(Integer.valueOf((String) map.get(REPLICA_COUNT)));
boolean isArbiter = map.containsKey(IS_ARBITER)
? Boolean.valueOf(map.get(IS_ARBITER).toString()) : Boolean.FALSE;
volume.setIsArbiter(isArbiter);
}
if (volume.getVolumeType().isStripedType()) {
volume.setStripeCount(Integer.valueOf((String) map.get(STRIPE_COUNT)));
}
if (volume.getVolumeType().isDispersedType()) {
volume.setDisperseCount(Integer.valueOf((String) map.get(DISPERSE_COUNT)));
volume.setRedundancyCount(Integer.valueOf((String) map.get(REDUNDANCY_COUNT)));
}
}
for(Object transportType : (Object[])map.get(TRANSPORT_TYPE)) {
volume.addTransportType(TransportType.valueOf((String)transportType));
}
String volStatus = (String)map.get(VOLUME_STATUS);
if(volStatus.toUpperCase().equals(VOLUME_STATUS_ONLINE)) {
volume.setStatus(GlusterStatus.UP);
} else {
volume.setStatus(GlusterStatus.DOWN);
}
try {
if (map.get(BRICKS_INFO) != null && ((Object[])map.get(BRICKS_INFO)).length > 0) {
volume.setBricks(getBricks(volume.getId(), (Object[])map.get(BRICKS_INFO), true));
} else {
volume.setBricks(getBricks(volume.getId(), (Object[])map.get(BRICKS), false));
}
} catch (Exception e) {
log.error("Could not populate bricks of volume '{}' on cluster '{}': {}", volume.getName(), clusterId, e.getMessage());
log.debug("Exception", e);
}
volume.setOptions(getOptions((Map<String, Object>)map.get(OPTIONS)));
return volume;
}
private Map<String, String> getOptions(Map<String, Object> map) {
Map<String, String> options = new HashMap<>();
for(Entry<String, Object> entry : map.entrySet()) {
options.put(entry.getKey(), (String)entry.getValue());
}
return options;
}
/**
* Gets list of bricks of the volume from given list of brick representations. This can return null in certain cases
* of failure e.g. if the brick representation contains an ip address which is mapped to more than servers in the
* database.
*/
private List<GlusterBrickEntity> getBricks(Guid volumeId, Object[] brickList, boolean withUuid) throws Exception {
List<GlusterBrickEntity> bricks = new ArrayList<>();
GlusterBrickEntity fetchedBrick;
int brickOrder = 0;
try {
for (Object brick : brickList) {
if (withUuid) {
fetchedBrick = getBrick(clusterId, volumeId, (Map<String, Object>) brick, brickOrder++);
} else {
fetchedBrick = getBrick(clusterId, volumeId, (String) brick, brickOrder++);
}
if (fetchedBrick != null) {
bricks.add(fetchedBrick);
}
}
} catch (Exception e) {
// We do not want the command to fail if bricks for one of the volumes could not be fetched. Hence log the
// exception and return null. The client should have special handling if bricks list of any of the volumes
// is null.
log.error("Error while populating bricks of volume '{}': {}", volumeId, e.getMessage());
log.debug("Exception", e);
return null;
}
return bricks;
}
/**
* Returns a brick object for given cluster and brick representation of the form hostnameOrIp:brickDir
* @param clusterId ID of the Cluster to which the brick belongs
* @param volumeId ID of the Volume to which the brick belongs
* @param brickInfo brick representation of the form hostnameOrIp:brickDir
* @param brickOrder Order number of the brick
* @return The brick object if representation passed is valid
*/
private GlusterBrickEntity getBrick(Guid clusterId, Guid volumeId, String brickInfo, int brickOrder) {
String[] brickParts = brickInfo.split(":", -1);
if (brickParts.length != 2) {
throw new RuntimeException("Invalid brick representation [" + brickInfo + "]");
}
String hostnameOrIp = brickParts[0];
String brickDir = brickParts[1];
VdsStatic server = dbUtils.getServer(clusterId, hostnameOrIp);
if (server == null) {
log.warn("Could not add brick '{}' to volume '{}' - server '{}' not found in cluster '{}'", brickInfo, volumeId, hostnameOrIp, clusterId);
return null;
}
return getBrickEntity(clusterId, volumeId, brickOrder, server, brickDir, null, null, false);
}
private GlusterBrickEntity getBrick(Guid clusterId, Guid volumeId, Map<String, Object> brickInfoMap, int brickOrder) {
String brickName = (String) brickInfoMap.get(NAME);
String[] brickParts = brickName.split(":", -1);
if (brickParts.length != 2) {
throw new RuntimeException("Invalid brick representation [" + brickName + "]");
}
String hostUuid = (String) brickInfoMap.get(HOST_UUID);
String brickDir = brickParts[1];
String hostAddress = brickParts[0];
boolean isArbiter = brickInfoMap.containsKey(IS_ARBITER)
? Boolean.valueOf(brickInfoMap.get(IS_ARBITER).toString()) : Boolean.FALSE;
GlusterServer glusterServer = dbUtils.getServerByUuid(Guid.createGuidFromString(hostUuid));
if (glusterServer == null) {
log.warn("Could not add brick '{}' to volume '{}' - server uuid '{}' not found in cluster '{}'", brickName, volumeId, hostUuid, clusterId);
return null;
}
VdsStatic server = DbFacade.getInstance().getVdsStaticDao().get(glusterServer.getId());
String networkAddress = null;
Guid networkId = null;
if (!server.getHostName().equals(hostAddress)) {
networkAddress = hostAddress;
Network network = getGlusterNetworkId(server, networkAddress);
if (network != null) {
networkId = network.getId();
} else {
log.warn("Could not associate brick '{}' of volume '{}' with correct network as no gluster network found in cluster '{}'",
brickName,
volumeId,
clusterId);
}
}
return getBrickEntity(clusterId, volumeId, brickOrder, server, brickDir, networkAddress, networkId, isArbiter);
}
private GlusterBrickEntity getBrickEntity(Guid clusterId,
Guid volumeId,
int brickOrder,
VdsStatic server,
String brickDir,
String networkAddress,
Guid networkId,
boolean isArbiter) {
GlusterBrickEntity brick = new GlusterBrickEntity();
brick.setVolumeId(volumeId);
brick.setBrickOrder(brickOrder);
brick.setBrickDirectory(brickDir);
brick.setIsArbiter(isArbiter);
brick.setServerId(server.getId());
brick.setServerName(server.getHostName());
brick.setNetworkAddress(networkAddress);
brick.setNetworkId(networkId);
return brick;
}
private Network getGlusterNetworkId(VdsStatic server, String networkAddress) {
List<Network> allNetworksInCluster =
DbFacade.getInstance().getNetworkDao().getAllForCluster(server.getClusterId());
for (Network network : allNetworksInCluster) {
if (network.getCluster().isGluster()
&& isSameNetworkAddress(server.getId(), network.getName(), networkAddress)) {
return network;
}
}
return null;
}
private Boolean isSameNetworkAddress(Guid hostId, String glusterNetworkName, String networkAddress) {
final List<VdsNetworkInterface> nics = DbFacade.getInstance().getInterfaceDao().getAllInterfacesForVds(hostId);
String brickAddress = null;
try {
brickAddress = InetAddress.getByName(networkAddress).getHostAddress();
} catch (UnknownHostException e) {
return false;
}
for (VdsNetworkInterface nic : nics) {
if (glusterNetworkName.equals(nic.getNetworkName())) {
return brickAddress.equals(nic.getIpv4Address());
}
}
return false;
}
public Map<Guid, GlusterVolumeEntity> getVolumes() {
return volumes;
}
}