/*
* This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH
* written by Rasto Levrinc.
*
* Copyright (C) 2009, LINBIT HA-Solutions GmbH.
* Copyright (C) 2011-2012, Rastislav Levrinc.
*
* DRBD Management Console is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* DRBD Management Console is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with drbd; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package lcmc.crm.domain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lcmc.common.domain.Application;
import lcmc.host.domain.Host;
import lcmc.common.domain.Value;
import lcmc.common.domain.ConvertCmdCallback;
import lcmc.logger.Logger;
import lcmc.logger.LoggerFactory;
import lcmc.common.domain.util.Tools;
import lcmc.cluster.service.ssh.ExecCommandConfig;
import lcmc.cluster.service.ssh.SshOutput;
import org.apache.commons.collections15.map.MultiKeyMap;
import javax.inject.Inject;
import javax.inject.Named;
/**
* This class parses pacemaker/heartbeat status, stores information
* in the hashes and provides methods to get this information.
*/
@Named
public final class ClusterStatus {
private static final Logger LOG = LoggerFactory.getLogger(ClusterStatus.class);
private volatile CibQuery cibQuery = new CibQuery();
private volatile CibQuery shadowCibQuery = new CibQuery();
private CrmXml crmXML;
/** On which node the resource is running or is a slave. */
private volatile Map<String, CrmXml.ResourceStatus> resStateMap = null;
private volatile PtestData ptestResult = null;
private String oldStatus = null;
private String oldCib = null;
private boolean oldAdvancedMode = false;
private Host host;
@Inject
private Application application;
/**
* Gets and parses metadata from pengine and crmd.
*/
public void init(final Host host, final CrmXml crmXML) {
this.host = host;
this.crmXML = crmXML;
final String command = host.getDistCommand("Heartbeat.getClusterMetadata",
(ConvertCmdCallback) null);
final SshOutput ret = host.captureCommandProgressIndicator(Tools.getString("Heartbeat.getClusterMetadata"),
new ExecCommandConfig().command(command)
.silentCommand()
.silentOutput());
final String output = ret.getOutput();
if (ret.getExitCode() != 0) {
return;
}
if (output != null) {
crmXML.parseClusterMetaData(output);
}
}
public String getGlobalParam(final String param) {
return cibQuery.getCrmConfig().get(param);
}
public String getRscDefaultsParameter(final String param, final Application.RunMode runMode) {
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getRscDefaultsParams().get(param);
} else {
return cibQuery.getRscDefaultsParams().get(param);
}
}
public String getRscDefaultsId(final Application.RunMode runMode) {
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getRscDefaultsId();
} else {
return cibQuery.getRscDefaultsId();
}
}
public String getParameter(final String hbId, final String param, final Application.RunMode runMode) {
final Map<String, String> params;
if (ptestResult != null && Application.isTest(runMode)) {
params = shadowCibQuery.getResourceParameters().get(hbId);
} else {
params = cibQuery.getResourceParameters().get(hbId);
}
if (params != null) {
return params.get(param);
}
return null;
}
public Map<String, String> getParametersNvpairsIds(final String hbId) {
return cibQuery.getResourceParametersNvpairsIds().get(hbId);
}
public String getDC() {
return cibQuery.getDC();
}
public void setDC(final String dc) {
cibQuery.setDC(dc);
}
public Map<String, String> getRscDefaultsValuePairs() {
return cibQuery.getRscDefaultsParams();
}
public Map<String, Value> getOpDefaultsValuePairs() {
return cibQuery.getOpDefaultsParams();
}
public Map<String, String> getParamValuePairs(final String hbId) {
return cibQuery.getResourceParameters().get(hbId);
}
public Set<String> getAllGroups() {
final Map<String, List<String>> groupsToResources = cibQuery.getGroupsToResources();
return groupsToResources.keySet();
}
public List<String> getGroupResources(final String group, final Application.RunMode runMode) {
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getGroupsToResources().get(group);
} else {
return cibQuery.getGroupsToResources().get(group);
}
}
public boolean isMaster(final String pmId) {
return cibQuery.getMasterList().contains(pmId);
}
public boolean isClone(final String cloneId) {
return cibQuery.getCloneToResource().containsKey(cloneId);
}
public ResourceAgent getResourceType(final String hbId) {
return cibQuery.getResourceType().get(hbId);
}
public boolean isInLRMOnHost(final String hostName, final String rscId, final Application.RunMode runMode) {
final Set<String> inLRMList = cibQuery.getInLRM().get(hostName.toLowerCase(Locale.US));
return inLRMList != null && inLRMList.contains(rscId);
}
public boolean isOrphaned(final String rscId) {
return cibQuery.getOrphaned().contains(rscId);
}
public String getResourceInstanceAttrId(final String hbId) {
return cibQuery.getResourceInstanceAttrId().get(hbId);
}
public CrmXml.ColocationData getColocationData(final String colId) {
return cibQuery.getColocationId().get(colId);
}
public List<CrmXml.ColocationData> getColocationDatas(final String rsc) {
return cibQuery.getColocationRsc().get(rsc);
}
public Map<String, List<CrmXml.ColocationData>> getColocationRscMap() {
return cibQuery.getColocationRsc();
}
public CrmXml.OrderData getOrderData(final String ordId) {
return cibQuery.getOrderId().get(ordId);
}
public List<CrmXml.OrderData> getOrderDatas(final String rsc) {
return cibQuery.getOrderRsc().get(rsc);
}
public Map<String, List<CrmXml.OrderData>> getOrderRscMap() {
return cibQuery.getOrderRsc();
}
public List<CrmXml.RscSetConnectionData> getRscSetConnections() {
return cibQuery.getRscSetConnections();
}
public List<CrmXml.RscSet> getRscSetsOrd(final String ordId) {
return cibQuery.getOrderIdRscSets().get(ordId);
}
public List<CrmXml.RscSet> getRscSetsCol(final String colId) {
return cibQuery.getColocationIdRscSets().get(colId);
}
public Iterable<String> getLocationIds(final String rsc, final Application.RunMode runMode) {
final List<String> locs;
if (ptestResult != null && Application.isTest(runMode)) {
locs = shadowCibQuery.getLocationsId().get(rsc);
} else {
locs = cibQuery.getLocationsId().get(rsc);
}
if (locs == null) {
return new ArrayList<String>();
} else {
return locs;
}
}
public HostLocation getScore(final String hbId, final String onHost, final Application.RunMode runMode) {
final Map<String, HostLocation> hostToScoreMap;
if (ptestResult != null && Application.isTest(runMode)) {
hostToScoreMap = shadowCibQuery.getLocations().get(hbId);
} else {
hostToScoreMap = cibQuery.getLocations().get(hbId);
}
if (hostToScoreMap != null) {
return hostToScoreMap.get(onHost.toLowerCase(Locale.US));
}
return null;
}
public HostLocation getPingScore(final String hbId, final Application.RunMode runMode) {
final HostLocation hostLocation;
if (ptestResult != null && Application.isTest(runMode)) {
hostLocation = shadowCibQuery.getPingLocations().get(hbId);
} else {
hostLocation = cibQuery.getPingLocations().get(hbId);
}
return hostLocation;
}
public String getLocationId(final String rsc, final String node, final Application.RunMode runMode) {
/* node should not have to be in lower case. */
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getResHostToLocId().get(rsc, node.toLowerCase(Locale.US));
} else {
return cibQuery.getResHostToLocId().get(rsc, node.toLowerCase(Locale.US));
}
}
public String getPingLocationId(final String rsc, final Application.RunMode runMode) {
/* node should not have to be in lower case. */
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getResPingToLocId().get(rsc);
} else {
return cibQuery.getResPingToLocId().get(rsc);
}
}
public String getMetaAttrsId(final String hbId) {
return cibQuery.getMetaAttrsId().get(hbId);
}
public Value getOperation(final String hbId, final String op, final String param) {
return cibQuery.getOperations().get(hbId, op, param);
}
public String getOperationsId(final String hbId) {
return cibQuery.getOperationsId().get(hbId);
}
public String getOpId(final String hbId, final String op) {
final Map<String, String> opIds = cibQuery.getResOpIds().get(hbId);
if (opIds == null) {
return null;
}
return opIds.get(op);
}
/**
* Returns crm id from serivce that this service has meta attributes from,
* or null.
*/
public String getMetaAttrsRef(final String hbId) {
return cibQuery.getMetaAttrsRefs().get(hbId);
}
/**
* Returns crm id from serivce that this service has operations from,
* or null.
*/
public String getOperationsRef(final String hbId) {
return cibQuery.getOperationsRefs().get(hbId);
}
public boolean isManaged(final String hbId, final Application.RunMode runMode) {
final PtestData pd = ptestResult;
if (pd != null && Application.isTest(runMode)) {
return pd.isManaged(hbId);
}
if (resStateMap == null) {
return true;
}
final CrmXml.ResourceStatus resourceStatus = resStateMap.get(hbId);
return resourceStatus == null || resourceStatus.isManagedByCrm();
}
public List<String> getRunningOnNodes(final String hbId, final Application.RunMode runMode) {
final PtestData pd = ptestResult;
if (pd != null && Application.isTest(runMode)) {
final List<String> ron = pd.getRunningOnNodes(hbId);
if (ron != null) {
Collections.sort(ron);
return ron;
}
}
if (resStateMap == null) {
return null;
}
final CrmXml.ResourceStatus resourceStatus = resStateMap.get(hbId);
if (resourceStatus == null) {
return null;
}
/* this one is already sorted. */
return resourceStatus.getRunningOnNodes();
}
public List<String> getSlaveOnNodes(final String hbId, final Application.RunMode runMode) {
final PtestData pd = ptestResult;
if (pd != null && Application.isTest(runMode)) {
final List<String> son = pd.getSlaveOnNodes(hbId);
if (son != null) {
Collections.sort(son);
return son;
}
}
if (resStateMap == null) {
return null;
}
final CrmXml.ResourceStatus resourceStatus = resStateMap.get(hbId);
if (resourceStatus == null) {
return null;
}
return resourceStatus.getSlaveOnNodes();
}
public List<String> getMasterOnNodes(final String hbId, final Application.RunMode runMode) {
final PtestData pd = ptestResult;
if (pd != null && Application.isTest(runMode)) {
final List<String> mon = pd.getMasterOnNodes(hbId);
if (mon != null) {
Collections.sort(mon);
return mon;
}
}
if (resStateMap == null) {
return null;
}
final CrmXml.ResourceStatus resourceStatus = resStateMap.get(hbId);
if (resourceStatus == null) {
return null;
}
return resourceStatus.getMasterOnNodes();
}
public Map<String, String> getAllocationScores(final String crmId, final Application.RunMode runMode) {
if (resStateMap == null) {
return Collections.emptyMap();
}
final CrmXml.ResourceStatus resourceStatus = resStateMap.get(crmId);
if (resourceStatus == null) {
return Collections.emptyMap();
}
return resourceStatus.getAllocationScores();
}
/**
* Returns String whether the node is online.
* "yes", "no" or null if it is unknown.
*/
public String isOnlineNode(final String node) {
return cibQuery.getNodeOnline().get(node.toLowerCase(Locale.US));
}
/**
* Sets whether the node is online.
* "yes", "no" or null if it is unknown.
*/
public void setOnlineNode(final String node, final String online) {
cibQuery.getNodeOnline().put(node.toLowerCase(Locale.US), online);
}
public boolean isPendingNode(final String node) {
return cibQuery.getNodePending().contains(node.toLowerCase(Locale.US));
}
/** Returns true if if node was fenced. */
public boolean isFencedNode(final String node) {
return cibQuery.getFencedNodes().contains(node.toLowerCase(Locale.US));
}
public String getFailCount(final String node, final String res, final Application.RunMode runMode) {
return cibQuery.getFailCount(node.toLowerCase(Locale.US), res);
}
public String getPingCount(final String node, final Application.RunMode runMode) {
if (ptestResult != null && Application.isTest(runMode)) {
return shadowCibQuery.getPingCount(node.toLowerCase(Locale.US));
}
return cibQuery.getPingCount(node.toLowerCase(Locale.US));
}
public Set<String> getFailedClones(final String node, final String res, final Application.RunMode runMode) {
return cibQuery.getResourceFailedCloneIds().get(node.toLowerCase(Locale.US), res);
}
public String getNodeParameter(final String node, final String param, final Application.RunMode runMode) {
final MultiKeyMap<String, String> nodeParams;
if (ptestResult != null && Application.isTest(runMode)) {
nodeParams = shadowCibQuery.getNodeParameters();
} else {
nodeParams = cibQuery.getNodeParameters();
}
if (nodeParams == null) {
return null;
} else {
return nodeParams.get(node.toLowerCase(Locale.US), param);
}
}
private boolean parseCommand(final String command, final List<String> data) {
final String[] commands = command.split("<<<>>>");
final String cmd = commands[0];
if (commands.length == 1) {
if ("fenced_nodes".equals(cmd)) {
} else if ("res_status".equals(cmd)) {
final String status = Tools.join("\n", data.toArray(new String[data.size()]));
if (!status.equals(oldStatus)) {
LOG.debug1("parseCommand: status update: " + host.getName());
oldStatus = status;
parseResStatus(status);
return true;
}
} else if ("cibadmin".equals(cmd)) {
final String cib = Tools.join("\n", data.toArray(new String[data.size()]));
final boolean advancedMode = application.isAdvancedMode();
if (!cib.equals(oldCib) || oldAdvancedMode != advancedMode) {
LOG.debug1("parseCommand: cib update: " + host.getName());
oldCib = cib;
oldAdvancedMode = advancedMode;
parseCibQuery(cib);
return true;
}
}
} else {
LOG.appError("parseCommand: unknown command: " + command);
}
return false;
}
public boolean parseStatus(final String status) {
final String[] lines = status.split("\n");
String command = null;
List<String> data = null;
boolean failed = false;
/* remove all hashes */
boolean updated = false;
for (final String linenl : lines) {
final String line = linenl.trim();
if ("---start---".equals(line) || "init".equals(line) || "evt:cib_changed".equals(line)) {
continue;
}
if ("---done---".equals(line)) {
break;
}
if (command == null) { /* start of command */
command = line;
data = null;
continue;
}
if (line.equals(">>>" + command)) { /* end of command */
if (!failed && parseCommand(command, data)) {
updated = true;
}
command = null;
continue;
}
/* first comes 'ok' */
if ("ok".equals(line)) {
data = new ArrayList<String>();
failed = false;
} else if ("fail".equals(line) || "None".equals(line)) {
failed = true;
data = new ArrayList<String>();
} else if (data != null) {
if (!failed) {
data.add(line);
}
} else {
LOG.appWarning("parseStatus: error parsing heartbeat status, line not ok: " + line + '\n' + status);
}
}
return updated;
}
private void parseResStatus(final String resStatus) {
resStateMap = crmXML.parseResStatus(resStatus);
}
private void parseCibQuery(final String query) {
cibQuery = crmXML.parseCibQuery(query);
}
public void setPtestResult(final PtestData ptestResult) {
this.ptestResult = ptestResult;
if (ptestResult == null) {
shadowCibQuery = new CibQuery();
} else {
shadowCibQuery = crmXML.parseCibQuery(
"<pcmk>" + ptestResult.getShadowCib() + "</pcmk>");
}
}
/** Return last known raw cib. */
public String getCibXml() {
return oldCib;
}
}