Java tutorial
/* * This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH * written by Rasto Levrinc. * * Copyright (C) 2009, LINBIT HA-Solutions GmbH. * * 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.data; import lcmc.gui.DrbdGraph; import lcmc.gui.resources.BlockDevInfo; import lcmc.utilities.Tools; import lcmc.utilities.ConvertCmdCallback; import lcmc.utilities.SSH; import lcmc.gui.resources.StringInfo; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import java.util.Map; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.ArrayList; import java.util.Locale; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.math.BigInteger; import org.apache.commons.collections15.map.MultiKeyMap; import org.apache.commons.collections15.map.LinkedMap; import org.apache.commons.collections15.keyvalue.MultiKey; /** * This class parses xml from drbdsetup and drbdadm, stores the * information in the hashes and provides methods to get this * information. * The xml is obtained with drbdsetp xml command and drbdadm dump-xml. * * @author Rasto Levrinc * @version $Id$ * */ public final class DrbdXML extends XML { // TODO: should that not be per host? /** Drbd config filename. */ private String configFile = "unknown"; /** Map from parameter name to the default value. */ private final Map<String, String> paramDefaultMap = new HashMap<String, String>(); /** Map from parameter name to its type. */ private final Map<String, String> paramTypeMap = new HashMap<String, String>(); /** Map from parameter name to its section. */ private final Map<String, String> paramSectionMap = new LinkedHashMap<String, String>(); /** Map from section to this section's parameters. */ private final Map<String, List<String>> sectionParamsMap = new LinkedHashMap<String, List<String>>(); /** Map from perameter name to its unit name (long). */ private final Map<String, String> paramUnitLongMap = new LinkedHashMap<String, String>(); /** Map from perameter name to its unit name. */ private final Map<String, String> paramDefaultUnitMap = new LinkedHashMap<String, String>(); /** Map from perameter name to its long description. */ private final Map<String, String> paramLongDescMap = new HashMap<String, String>(); /** Map from perameter name to its minimum value. */ private final Map<String, BigInteger> paramMinMap = new LinkedHashMap<String, BigInteger>(); /** Map from perameter name to its maximum value. */ private final Map<String, BigInteger> paramMaxMap = new LinkedHashMap<String, BigInteger>(); /** Map from perameter name to its correct value. */ private final Map<String, Boolean> paramCorrectValueMap = new HashMap<String, Boolean>(); /** Map from perameter name to its items if there is a choice list. */ private final Map<String, List<Object>> paramItemsMap = new LinkedHashMap<String, List<Object>>(); /** List of all parameters. */ private final List<String> parametersList = new ArrayList<String>(); /** List of all gloval parameters. */ private final List<String> globalParametersList = new ArrayList<String>(); /** List of all required parameters. */ private final List<String> requiredParametersList = new ArrayList<String>(); /** Map from resource option to the value. */ private final Map<String, Map<String, String>> optionsMap = new HashMap<String, Map<String, String>>(); /** List with drbd resources. */ private final List<String> resourceList = new ArrayList<String>(); /** Map from drbd resource name to the drbd device. */ private final MultiKeyMap<String, String> resourceDeviceMap = MultiKeyMap .decorate(new LinkedMap<MultiKey<String>, String>()); /** Map from drbd device to the drbd resource name. */ private final Map<String, String> deviceResourceMap = new HashMap<String, String>(); /** Map from drbd device to the drbd volume. */ private final Map<String, String> deviceVolumeMap = new HashMap<String, String>(); /** Map from drbd resource name and the host to the block device. */ private final MultiKeyMap<String, Map<String, String>> resourceHostDiskMap = new MultiKeyMap<String, Map<String, String>>(); /** Map from drbd resource name and the host to the ip. */ private final Map<String, Map<String, String>> resourceHostIpMap = new HashMap<String, Map<String, String>>(); /** Map from drbd resource name and the host to the port. */ private final Map<String, Map<String, String>> resourceHostPortMap = new HashMap<String, Map<String, String>>(); /** Map from drbd resource name and the host to the meta disk. */ private final MultiKeyMap<String, Map<String, String>> resourceHostMetaDiskMap = new MultiKeyMap<String, Map<String, String>>(); /** Map from drbd resource name and the host to the meta disk index. */ private final MultiKeyMap<String, Map<String, String>> resourceHostMetaDiskIndexMap = new MultiKeyMap<String, Map<String, String>>(); /** Map from host to the boolean value if drbd is loaded on this host. */ private final Map<String, Boolean> hostDrbdLoadedMap = new HashMap<String, Boolean>(); /** Whether there are unknown sections in the config. */ boolean unknownSections = false; /** Whether there is proxy in the config. */ boolean proxyDetected = false; /** Global section. */ public static final String GLOBAL_SECTION = "global"; /** DRBD protocol C, that is a default. */ private static final String PROTOCOL_C = "C / Synchronous"; /** DRBD communication protocols. */ static final StringInfo[] PROTOCOLS = { new StringInfo("A / Asynchronous", "A", null), new StringInfo("B / Semi-Synchronous", "B", null), new StringInfo(PROTOCOL_C, "C", null) }; /** Some non advanced parameters. */ static final List<String> NOT_ADVANCED_PARAMS = new ArrayList<String>(); static { NOT_ADVANCED_PARAMS.add("rate"); NOT_ADVANCED_PARAMS.add("protocol"); NOT_ADVANCED_PARAMS.add("fence-peer"); NOT_ADVANCED_PARAMS.add("wfc-timeout"); NOT_ADVANCED_PARAMS.add("degr-wfc-timeout"); NOT_ADVANCED_PARAMS.add("become-primary-on"); NOT_ADVANCED_PARAMS.add("timeout"); NOT_ADVANCED_PARAMS.add("allow-two-primaries"); NOT_ADVANCED_PARAMS.add("fencing"); NOT_ADVANCED_PARAMS.add("after"); /* before 8.4 */ NOT_ADVANCED_PARAMS.add("resync-after"); NOT_ADVANCED_PARAMS.add("usage-count"); /* global */ } /** Access types of some parameters. */ static final Map<String, ConfigData.AccessType> PARAM_ACCESS_TYPE = new HashMap<String, ConfigData.AccessType>(); static { PARAM_ACCESS_TYPE.put("rate", ConfigData.AccessType.OP); } /** Yes / true drbd config value. */ public static final String CONFIG_YES = "yes"; /** No / false drbd config value. */ public static final String CONFIG_NO = "no"; /** Hardcoded defaults, for options that have it but we don't get it from the drbdsetup. */ static final Map<String, String> HARDCODED_DEFAULTS = new HashMap<String, String>(); static { HARDCODED_DEFAULTS.put("usage-count", ""); HARDCODED_DEFAULTS.put("disable-ip-verification", CONFIG_NO); HARDCODED_DEFAULTS.put("protocol", "C"); HARDCODED_DEFAULTS.put("after-sb-0pri", "disconnect"); HARDCODED_DEFAULTS.put("after-sb-1pri", "disconnect"); HARDCODED_DEFAULTS.put("after-sb-2pri", "disconnect"); HARDCODED_DEFAULTS.put("rr-conflict", "disconnect"); HARDCODED_DEFAULTS.put("on-io-error", "pass_on"); HARDCODED_DEFAULTS.put("fencing", "dont-care"); HARDCODED_DEFAULTS.put("on-no-data-accessible", "io-error"); HARDCODED_DEFAULTS.put("on-congestion", "block"); } /** Prepares a new <code>DrbdXML</code> object. */ public DrbdXML(final Host[] hosts, final Map<Host, String> drbdParameters) { super(); addSpecialParameter("resource", "name", true); for (final Host host : hosts) { String output = null; if (drbdParameters.get(host) == null) { final String command = host.getDistCommand("Drbd.getParameters", (ConvertCmdCallback) null); final SSH.SSHOutput ret = Tools.execCommand(host, command, null, /* ExecCallback */ false, /* outputVisible */ SSH.DEFAULT_COMMAND_TIMEOUT); if (ret.getExitCode() != 0) { return; } output = ret.getOutput(); if (output == null) { return; } drbdParameters.put(host, output); } else { output = drbdParameters.get(host); } /* TODO: move this part somewhere else, it should be called once per invocation or interactively. (drbd-get-xml) */ final String[] lines = output.split("\\r?\\n"); final Pattern bp = Pattern.compile("^<command name=\"(.*?)\".*"); final Pattern ep = Pattern.compile("^</command>$"); final StringBuilder xml = new StringBuilder(); String section = null; for (final String line : lines) { final Matcher m = bp.matcher(line); if (m.matches()) { section = m.group(1); } if (section != null) { xml.append(line); xml.append('\n'); final Matcher m2 = ep.matcher(line); if (m2.matches()) { parseSection(section, xml.toString(), host, hosts); section = null; xml.delete(0, xml.length()); } } } if (!parametersList.contains("protocol")) { /* prior 8.4 */ addParameter("resource", "protocol", PROTOCOL_C, PROTOCOLS, true); } } } /** Returns the filename of the drbd config file. */ String getConfigFile() { return configFile; } /** Returns config from server. */ public String getConfig(final Host host) { if (!host.isConnected()) { return null; } final String command2 = host.getDistCommand("Drbd.getConfig", (ConvertCmdCallback) null); final SSH.SSHOutput ret = Tools.execCommand(host, command2, null, /* ExecCallback */ false, /* outputVisible */ SSH.DEFAULT_COMMAND_TIMEOUT); if (ret.getExitCode() == 0) { final StringBuffer confSB = new StringBuffer(ret.getOutput()); final String conf = host.getOutput("drbd", confSB); return conf; } return null; } /** * Retrieves and updates all the data. This should be called if there is * a drbd event. */ public void update(final String configString) { if (configString != null && !configString.equals("")) { parseConfig(configString); } } /** Returns all drbd parameters. */ public String[] getParameters() { return parametersList.toArray(new String[parametersList.size()]); } /** Gets short description for the parameter. */ public String getParamShortDesc(final String param) { final StringBuilder name = new StringBuilder(param.replaceAll("\\-", " ")); name.replace(0, 1, name.substring(0, 1).toUpperCase()); if (paramUnitLongMap.containsKey(param)) { name.append(" (" + paramUnitLongMap.get(param) + ")"); } return name.toString(); } /** Gets long description for the parameter. */ public String getParamLongDesc(final String param) { return paramLongDescMap.get(param); } /** Returns the long name of the unit of the specified parameter. */ public String getUnitLong(final String param) { return paramUnitLongMap.get(param); } /** Returns the default unit of the specified parameter. */ public String getDefaultUnit(final String param) { return paramDefaultUnitMap.get(param); } /** Returns whether the parameter has a unit prefix. */ public boolean hasUnitPrefix(final String param) { final String unit = paramUnitLongMap.get(param); return paramDefaultUnitMap.containsKey(param) && (unit == null || "bytes".equals(unit) || "bytes/second".equals(unit)); } /** * Returns parameter type, that is one of the following: * numeric * string * boolean * handle * . */ public String getParamType(final String param) { return paramTypeMap.get(param); } /** Gets default for the parameter. */ public String getParamDefault(final String param) { final String defaultValue = paramDefaultMap.get(param); if (defaultValue == null) { return ""; } if (hasUnitPrefix(param)) { final StringBuilder defaultValueBuf = new StringBuilder(defaultValue); final String unit = getDefaultUnit(param); if (unit != null) { defaultValueBuf.append(unit); } return defaultValueBuf.toString(); } return defaultValue; } /** Gets preferred value for the parameter. */ public String getParamPreferred(final String param) { // TODO: return null; } /** Returns section in which this param is in. */ public String getSection(final String param) { return paramSectionMap.get(param); } /** * Checks parameter according to its type. Returns false if value is wrong. */ public boolean checkParam(final String param, final String rawValue) { final String type = paramTypeMap.get(param); boolean correctValue = true; String value = rawValue; String unit = null; if (rawValue != null && hasUnitPrefix(param)) { /* number with unit */ final Pattern p = Pattern.compile("\\d*([kmgtsKMGTS])"); final Matcher m = p.matcher(rawValue); if (m.matches()) { /* remove unit from value */ unit = m.group(1).toUpperCase(); value = rawValue.substring(0, rawValue.length() - unit.length()); } } if (value == null || "".equals(value)) { if (isRequired(param)) { correctValue = false; } else { correctValue = true; } } else if ("boolean".equals(type)) { if (!value.equals(CONFIG_YES) && !value.equals(CONFIG_NO)) { correctValue = false; } } else if ("numeric".equals(type)) { final Pattern p = Pattern.compile("(-?\\d+)|\\d*"); final Matcher m = p.matcher(value); if (!m.matches()) { correctValue = false; } else if ((unit == null || "k".equalsIgnoreCase(unit) || "m".equalsIgnoreCase(unit) || "g".equalsIgnoreCase(unit) || "t".equalsIgnoreCase(unit)) && "K".equalsIgnoreCase(getDefaultUnit(param))) { /* except sectors */ long v; if (unit == null) { v = Long.parseLong(rawValue) / 1024; } else { v = Tools.convertToKilobytes(rawValue); } if (paramMaxMap.get(param) != null && v > paramMaxMap.get(param).longValue()) { correctValue = false; } else if (paramMinMap.get(param) != null && v < paramMinMap.get(param).longValue()) { correctValue = false; } } else if (!"s".equalsIgnoreCase(unit)) { final long v = Tools.convertUnits(rawValue); if (paramMaxMap.get(param) != null && v > paramMaxMap.get(param).longValue()) { correctValue = false; } else if (paramMinMap.get(param) != null && v < paramMinMap.get(param).longValue()) { correctValue = false; } } } else { correctValue = true; } paramCorrectValueMap.put(param, correctValue); return correctValue; } /** Returns whether parameter expects integer value. */ public boolean isInteger(final String param) { final String type = paramTypeMap.get(param); return "numeric".equals(type); } /** Returns whether parameter is read only. */ public boolean isLabel(final String param) { return false; } /** Returns whether parameter expects string value. */ public boolean isStringType(final String param) { final String type = paramTypeMap.get(param); return "string".equals(type); } /** Checks in the cache if the parameter was correct. */ boolean checkParamCache(final String param) { return paramCorrectValueMap.get(param).booleanValue(); } /** * Adds parameter to the specified section. This parameter will be not * used in the generated config. */ private void addSpecialParameter(final String section, final String param, final boolean required) { if (!parametersList.contains(param)) { parametersList.add(param); if (required) { requiredParametersList.add(param); } paramTypeMap.put(param, "string"); paramSectionMap.put(param, section); } } /** Add paremeter with choice combo box. */ private void addParameter(final String section, final String param, final String defaultValue, final Object[] items, final boolean required) { addParameter(section, param, defaultValue, required); final List<Object> l = new ArrayList<Object>(); for (int i = 0; i < items.length; i++) { if (!l.contains(items[i])) { l.add(items[i]); } } paramItemsMap.put(param, l); paramTypeMap.put(param, "handler"); } /** Adds parameter to the specified section. */ private void addParameter(final String section, final String param, final boolean required) { addSpecialParameter(section, param, required); sectionParamsMap.put(section, new ArrayList<String>()); sectionParamsMap.get(section).add(param); } /** Adds parameter with a default value to the specified section. */ private void addParameter(final String section, final String param, final String defaultValue, final boolean required) { addParameter(section, param, required); paramDefaultMap.put(param, defaultValue); } /** Adds parameter with the specified type. */ private void addParameter(final String section, final String param, final String defaultValue, final boolean required, final String type) { addParameter(section, param, defaultValue, required); paramTypeMap.put(param, type); } /** Returns array with all the sections. */ public String[] getSections() { return sectionParamsMap.keySet().toArray(new String[sectionParamsMap.size()]); } /** Returns parameters for the specified section. */ public String[] getSectionParams(final String section) { final List<String> params = sectionParamsMap.get(section); if (params == null) { return new String[0]; } return params.toArray(new String[params.size()]); } /** Returns parameters for the global section. */ public String[] getGlobalParams() { return globalParametersList.toArray(new String[globalParametersList.size()]); } /** Returns possible choices. */ public Object[] getPossibleChoices(final String param) { final List<Object> items = paramItemsMap.get(param); if (items == null) { return null; } else { return items.toArray(new Object[items.size()]); } } /** Returns whether parameter is required. */ public boolean isRequired(final String param) { return requiredParametersList.contains(param); } /** Returns whether parameter is advanced. */ public boolean isAdvanced(final String param) { return !isRequired(param) && !NOT_ADVANCED_PARAMS.contains(param); } /** Returns access type of the parameter. */ public ConfigData.AccessType getAccessType(final String param) { final ConfigData.AccessType at = PARAM_ACCESS_TYPE.get(param); if (at == null) { return ConfigData.AccessType.ADMIN; } return at; } /** Parses the global node in the drbd config. */ private void parseConfigGlobalNode(final Node globalNode, final Map<String, String> nameValueMap) { final NodeList options = globalNode.getChildNodes(); for (int i = 0; i < options.getLength(); i++) { final Node option = options.item(i); final String nodeName = option.getNodeName(); if ("dialog-refresh".equals(nodeName)) { final String dialogRefresh = getAttribute(option, "refresh"); nameValueMap.put(nodeName, dialogRefresh); } else if ("minor-count".equals(nodeName)) { final String minorCount = getAttribute(option, "count"); nameValueMap.put(nodeName, minorCount); } else if ("disable-ip-verification".equals(nodeName)) { nameValueMap.put(nodeName, CONFIG_YES); } else if ("usage-count".equals(nodeName)) { /* does not come from drbdadm */ /* TODO: "count" is guessed. */ final String usageCount = getAttribute(option, "count"); if (usageCount != null) { nameValueMap.put(nodeName, usageCount); } } } } /** Parses command xml for parameters and fills up the hashes. */ private void parseSection(final String section, final String xml, final Host host, final Host[] hosts) { final Document document = getXMLDocument(xml); /* get root <command> */ final Node commandNode = getChildNode(document, "command"); if (commandNode == null) { return; } final NodeList options = commandNode.getChildNodes(); for (int i = 0; i < options.getLength(); i++) { final Node optionNode = options.item(i); /* <option> */ if (optionNode.getNodeName().equals("option")) { final String name = getAttribute(optionNode, "name"); final String type = getAttribute(optionNode, "type"); if ("flag".equals(type)) { /* ignore flags */ continue; } if ("handler".equals(type)) { final List<Object> items = new ArrayList<Object>(); items.add(""); paramItemsMap.put(name, items); paramDefaultMap.put(name, HARDCODED_DEFAULTS.get(name)); } else if ("boolean".equals(type)) { final List<Object> l = new ArrayList<Object>(); l.add(CONFIG_YES); l.add(CONFIG_NO); paramItemsMap.put(name, l); paramDefaultMap.put(name, CONFIG_NO); } if ("fence-peer".equals(name)) { final List<Object> l = new ArrayList<Object>(); l.add(""); if (!"".equals(host.getArch())) { l.add(host.getHeartbeatLibPath() + "/drbd-peer-outdater -t 5"); } l.add("/usr/lib/drbd/crm-fence-peer.sh"); paramItemsMap.put(name, l); } else if ("after-resync-target".equals(name)) { final List<Object> l = new ArrayList<Object>(); l.add(""); l.add("/usr/lib/drbd/crm-unfence-peer.sh"); paramItemsMap.put(name, l); } else if ("split-brain".equals(name)) { final List<Object> l = new ArrayList<Object>(); l.add(""); l.add("/usr/lib/drbd/notify-split-brain.sh root"); paramItemsMap.put(name, l); } else if ("become-primary-on".equals(name)) { final List<Object> l = new ArrayList<Object>(); l.add(""); l.add("both"); for (final Host h : hosts) { l.add(h.getName()); } paramItemsMap.put(name, l); } else if ("verify-alg".equals(name) || "csums-alg".equals(name) || "data-integrity-alg".equals(name) || "cram-hmac-alg".equals(name)) { final List<Object> l = new ArrayList<Object>(); l.add(""); for (final String cr : host.getCryptoModules()) { l.add(cr); } paramItemsMap.put(name, l); } final NodeList optionInfos = optionNode.getChildNodes(); for (int j = 0; j < optionInfos.getLength(); j++) { final Node optionInfo = optionInfos.item(j); final String tag = optionInfo.getNodeName(); /* <min>, <max>, <handler>, <default> */ if ("min".equals(tag)) { paramMinMap.put(name, new BigInteger(getText(optionInfo))); } else if ("max".equals(tag)) { paramMaxMap.put(name, new BigInteger(getText(optionInfo))); } else if ("handler".equals(tag)) { paramItemsMap.get(name).add(getText(optionInfo)); } else if ("default".equals(tag)) { paramDefaultMap.put(name, getText(optionInfo)); } else if ("unit".equals(tag)) { paramUnitLongMap.put(name, getText(optionInfo)); } else if ("unit_prefix".equals(tag)) { if (!"after".equals(name) && !"resync-after".equals(name)) { String option = getText(optionInfo); if (!"s".equals(option)) { /* "s" is an exception */ option = option.toUpperCase(Locale.US); } if ("1".equals(option)) { option = ""; } paramDefaultUnitMap.put(name, option); } } else if ("desc".equals(tag)) { paramLongDescMap.put(name, getText(optionInfo)); } } paramTypeMap.put(name, type); if (!GLOBAL_SECTION.equals(section) && !parametersList.contains(name)) { parametersList.add(name); } if (!"resource".equals(section) && !globalParametersList.contains(name) && !("syncer".equals(section) && "after".equals(name)) && !"resync-after".equals(name)) { globalParametersList.add(name); } paramSectionMap.put(name, section); if (!sectionParamsMap.containsKey(section)) { sectionParamsMap.put(section, new ArrayList<String>()); } if (!sectionParamsMap.get(section).contains(name)) { sectionParamsMap.get(section).add(name); } } } } /** Parses section node and creates map with option name value pairs. */ private void parseConfigSectionNode(final Node sectionNode, final Map<String, String> nameValueMap) { final NodeList options = sectionNode.getChildNodes(); for (int i = 0; i < options.getLength(); i++) { final Node option = options.item(i); if (option.getNodeName().equals("option")) { final String name = getAttribute(option, "name"); String value = getAttribute(option, "value"); if (value == null) { /* boolean option */ value = CONFIG_YES; } else if (hasUnitPrefix(name)) { /* with unit */ final Pattern p = Pattern.compile("\\d+([kmgs])"); final Matcher m = p.matcher(value); if (m.matches()) { /* uppercase unit in value */ final String unit = m.group(1).toUpperCase(); value = value.substring(0, value.length() - unit.length()) + unit; } } nameValueMap.put(name, value); } } } /** Parses host node in the drbd config. */ private void parseHostConfig(final String resName, final Node hostNode) { final String hostName = getAttribute(hostNode, "name"); parseVolumeConfig(hostName, resName, hostNode); /* before 8.4 */ final NodeList options = hostNode.getChildNodes(); for (int i = 0; i < options.getLength(); i++) { final Node option = options.item(i); if (option.getNodeName().equals("volume")) { parseVolumeConfig(hostName, resName, option); } else if (option.getNodeName().equals("address")) { final String ip = getText(option); final String port = getAttribute(option, "port"); /* ip */ Map<String, String> hostIpMap = resourceHostIpMap.get(resName); if (hostIpMap == null) { hostIpMap = new HashMap<String, String>(); resourceHostIpMap.put(resName, hostIpMap); } hostIpMap.put(hostName, ip); /* port */ Map<String, String> hostPortMap = resourceHostPortMap.get(resName); if (hostPortMap == null) { hostPortMap = new HashMap<String, String>(); resourceHostPortMap.put(resName, hostPortMap); } hostPortMap.put(hostName, port); } } } /** Parses host node in the drbd config. */ private void parseVolumeConfig(final String hostName, final String resName, final Node volumeNode) { String volumeNr = getAttribute(volumeNode, "vnr"); if (volumeNr == null) { volumeNr = "0"; } final NodeList options = volumeNode.getChildNodes(); for (int i = 0; i < options.getLength(); i++) { final Node option = options.item(i); if (option.getNodeName().equals("device")) { String device = getText(option); if (device != null && "".equals(device)) { final String minor = getAttribute(option, "minor"); device = "/dev/drbd" + minor; } resourceDeviceMap.put(resName, volumeNr, device); deviceResourceMap.put(device, resName); deviceVolumeMap.put(device, volumeNr); } else if (option.getNodeName().equals("disk")) { final String disk = getText(option); Map<String, String> hostDiskMap = resourceHostDiskMap.get(resName, volumeNr); if (hostDiskMap == null) { hostDiskMap = new HashMap<String, String>(); resourceHostDiskMap.put(resName, volumeNr, hostDiskMap); } hostDiskMap.put(hostName, disk); } else if (option.getNodeName().equals("meta-disk") || option.getNodeName().equals("flexible-meta-disk")) { final boolean flexible = option.getNodeName().equals("flexible-meta-disk"); final String metaDisk = getText(option); String metaDiskIndex = null; if (!flexible) { metaDiskIndex = getAttribute(option, "index"); } if (metaDiskIndex == null) { metaDiskIndex = "Flexible"; } /* meta-disk */ Map<String, String> hostMetaDiskMap = resourceHostMetaDiskMap.get(resName, volumeNr); if (hostMetaDiskMap == null) { hostMetaDiskMap = new HashMap<String, String>(); resourceHostMetaDiskMap.put(resName, volumeNr, hostMetaDiskMap); } hostMetaDiskMap.put(hostName, metaDisk); /* meta-disk index */ Map<String, String> hostMetaDiskIndexMap = resourceHostMetaDiskIndexMap.get(resName, volumeNr); if (hostMetaDiskIndexMap == null) { hostMetaDiskIndexMap = new HashMap<String, String>(); resourceHostMetaDiskIndexMap.put(resName, volumeNr, hostMetaDiskIndexMap); } hostMetaDiskIndexMap.put(hostName, metaDiskIndex); } else if (option.getNodeName().equals("address")) { /* since 8.4, it's outside of volume */ final String ip = getText(option); final String port = getAttribute(option, "port"); /* ip */ Map<String, String> hostIpMap = resourceHostIpMap.get(resName); if (hostIpMap == null) { hostIpMap = new HashMap<String, String>(); resourceHostIpMap.put(resName, hostIpMap); } hostIpMap.put(hostName, ip); /* port */ Map<String, String> hostPortMap = resourceHostPortMap.get(resName); if (hostPortMap == null) { hostPortMap = new HashMap<String, String>(); resourceHostPortMap.put(resName, hostPortMap); } hostPortMap.put(hostName, port); } else if (option.getNodeName().equals("proxy")) { if (!proxyDetected) { Tools.appWarning("unsuported feature: proxy"); Tools.progressIndicatorFailed(hostName, "unsupported feature: proxy"); proxyDetected = true; } } } } /** Returns map with hosts as keys and disks as values. */ public Map<String, String> getHostDiskMap(final String resName, final String volumeNr) { return resourceHostDiskMap.get(resName, volumeNr); } /** Returns map with hosts as keys and ips as values. */ Map<String, String> getHostIpMap(final String resName) { return resourceHostIpMap.get(resName); } /** Gets virtual net interface port for a host and a resource. */ public String getVirtualInterfacePort(final String hostName, final String resName) { if (resourceHostPortMap.containsKey(resName)) { return resourceHostPortMap.get(resName).get(hostName); } return null; } /** Gets virtual net interface for a host and a resource. */ public String getVirtualInterface(final String hostName, final String resName) { if (resourceHostIpMap.containsKey(resName)) { return resourceHostIpMap.get(resName).get(hostName); } return null; } /** Gets meta-disk block device for a host and a resource. */ public String getMetaDisk(final String hostName, final String resName, final String volumeNr) { final Map<String, String> hostMetaDiskMap = resourceHostMetaDiskMap.get(resName, volumeNr); if (hostMetaDiskMap != null) { return hostMetaDiskMap.get(hostName); } return null; } /** * Gets meta-disk index for a host and a resource. Index can * be keyword 'flexible'. In this case 'flexible-meta-disk option * will be generated in the config. */ public String getMetaDiskIndex(final String hostName, final String resName, final String volumeNr) { final Map<String, String> hostMetaDiskIndexMap = resourceHostMetaDiskIndexMap.get(resName, volumeNr); if (hostMetaDiskIndexMap != null) { return hostMetaDiskIndexMap.get(hostName); } return null; } /** Parses resource xml. */ private void parseConfigResourceNode(final Node resourceNode, final String resName) { final String resProtocol = getAttribute(resourceNode, "protocol"); if (resProtocol != null) { Map<String, String> nameValueMap = optionsMap.get(resName + "." + "resource"); if (nameValueMap == null) { nameValueMap = new HashMap<String, String>(); } else { optionsMap.remove(resName + "." + "resource"); } nameValueMap.put("protocol", resProtocol); optionsMap.put(resName + "." + "resource", nameValueMap); } final NodeList c = resourceNode.getChildNodes(); for (int i = 0; i < c.getLength(); i++) { final Node n = c.item(i); if (n.getNodeName().equals("host")) { /* <host> */ parseHostConfig(resName, n); } else if (n.getNodeName().equals("section")) { /* <resource> */ final String secName = getAttribute(n, "name"); Map<String, String> nameValueMap = optionsMap.get(resName + "." + secName); if (nameValueMap == null) { nameValueMap = new HashMap<String, String>(); } else { optionsMap.remove(resName + "." + secName); } parseConfigSectionNode(n, nameValueMap); optionsMap.put(resName + "." + secName, nameValueMap); if (!sectionParamsMap.containsKey(secName) && !sectionParamsMap.containsKey(secName + "-options")) { Tools.appWarning("DRBD: unknown section: " + secName); if (!unknownSections) { /* unknown section, so it's not removed. */ Tools.progressIndicatorFailed("DRBD: unknown section: " + secName); unknownSections = true; } } } } } /** Parses config xml from drbdadm dump-xml. */ private void parseConfig(final String configXML) { final int start = configXML.indexOf("<config"); if (start < 0) { return; } final Document document = getXMLDocument(configXML.substring(start)); if (document == null) { return; } /* get root <config> */ final Node configNode = getChildNode(document, "config"); if (configNode == null) { return; } /* config file=".." TODO: */ configFile = getAttribute(configNode, "file"); final NodeList resources = configNode.getChildNodes(); Map<String, String> globalNameValueMap = optionsMap.get(GLOBAL_SECTION); if (globalNameValueMap == null) { globalNameValueMap = new HashMap<String, String>(); optionsMap.put(GLOBAL_SECTION, globalNameValueMap); } globalNameValueMap.put("usage-count", "yes"); globalNameValueMap.put("disable-ip-verification", CONFIG_NO); for (int i = 0; i < resources.getLength(); i++) { final Node resourceNode = resources.item(i); /* <global> */ if (resourceNode.getNodeName().equals(GLOBAL_SECTION)) { parseConfigGlobalNode(resourceNode, globalNameValueMap); } /* <common> */ if (resourceNode.getNodeName().equals("common")) { parseConfigResourceNode(resourceNode, "Section.Common"); } /* <resource> */ if (resourceNode.getNodeName().equals("resource")) { final String resName = getAttribute(resourceNode, "name"); if (!resourceList.contains(resName)) { resourceList.add(resName); } parseConfigResourceNode(resourceNode, resName); } } } /** Returns value from drbd global config identified by option name. */ public String getGlobalConfigValue(final String optionName) { final Map<String, String> option = optionsMap.get(GLOBAL_SECTION); String value = null; if (option != null) { value = option.get(optionName); } if (value == null) { return ""; } return value; } /** * Returns value from drbd config identified by resource, section and * option name. */ public String getConfigValue(final String res, final String section, final String optionName) { final Map<String, String> option = optionsMap.get(res + "." + section); String value = null; if (option != null) { value = option.get(optionName); } if (value == null) { return ""; } return value; } /** Returns config value from the common section. */ public String getCommonConfigValue(final String section, final String optionName) { String value = null; final Map<String, String> option = optionsMap.get("Section.Common." + section); if (option != null) { value = option.get(optionName); } if (value == null) { return ""; } return value; } /** Returns array of resource. */ public String[] getResources() { return resourceList.toArray(new String[resourceList.size()]); } /** * Returns drbd device of the resource. Although there can be different * drbd devices on different hosts. We do not allow that. */ public String getDrbdDevice(final String res, final String volumeNr) { return resourceDeviceMap.get(res, volumeNr); } /** Returns map from res and volume to drbd device. */ public MultiKeyMap<String, String> getResourceDeviceMap() { return resourceDeviceMap; } /** Gets block device object from device number. Can return null. */ private BlockDevInfo getBlockDevInfo(final String devNr, final String hostName, final DrbdGraph drbdGraph) { BlockDevInfo bdi = null; final String device = "/dev/drbd" + devNr; final String resName = deviceResourceMap.get(device); String volumeNr = deviceVolumeMap.get(device); if (volumeNr == null) { volumeNr = "0"; } if (resName != null) { final Map<String, String> hostDiskMap = resourceHostDiskMap.get(resName, volumeNr); if (hostDiskMap != null) { final String disk = hostDiskMap.get(hostName); if (disk != null) { bdi = drbdGraph.findBlockDevInfo(hostName, disk); } } } return bdi; } /** Returns whether the drbd is loaded. */ boolean isDrbdLoaded(final String hostName) { final Boolean l = hostDrbdLoadedMap.get(hostName); if (l != null) { return l.booleanValue(); } return true; } /** * Parses events from drbd kernel module obtained via drbdsetup .. events * command and stores the values in the BlockDevice object. */ public boolean parseDrbdEvent(final String hostName, final DrbdGraph drbdGraph, final String rawOutput) { if (rawOutput == null || hostName == null) { return false; } final String output = rawOutput.trim(); if ("".equals(output)) { return false; } if ("No response from the DRBD driver! Is the module loaded?".equals(output)) { if (hostDrbdLoadedMap.get(hostName)) { hostDrbdLoadedMap.put(hostName, false); return true; } else { return false; } } else { hostDrbdLoadedMap.put(hostName, true); } /* since drbd 8.3 there is ro: instead of st: */ /* since drbd 8.4 there is ro: instead of st: */ Pattern p = Pattern.compile("^(\\d+)\\s+ST\\s+(\\S+)\\s+\\{\\s+cs:(\\S+)\\s+" + "(?:st|ro):(\\S+)/(\\S+)\\s+ds:(\\S+)/(\\S+)\\s+(\\S+).*?"); Matcher m = p.matcher(output); final Pattern pDev = Pattern.compile("^(\\d+),(\\S+)\\[(\\d+)\\]$"); if (m.matches()) { /* String counter = m.group(1); // not used */ final String devNrString = m.group(2); final String cs = m.group(3); final String ro1 = m.group(4); final String ro2 = m.group(5); final String ds1 = m.group(6); final String ds2 = m.group(7); final String flags = m.group(8); final Matcher mDev = pDev.matcher(devNrString); String devNr = devNrString; String res = ""; // TODO: unused */ String volumeNr = "0"; if (mDev.matches()) { /* since 8.4 */ devNr = mDev.group(1); res = mDev.group(2); volumeNr = mDev.group(3); } /* get blockdevice object from device */ final BlockDevInfo bdi = getBlockDevInfo(devNr, hostName, drbdGraph); if (bdi != null) { if (bdi.getBlockDevice().isDifferent(cs, ro1, ds1, flags)) { bdi.getBlockDevice().setConnectionState(cs); bdi.getBlockDevice().setNodeState(ro1); bdi.getBlockDevice().setDiskState(ds1); bdi.getBlockDevice().setDrbdFlags(flags); bdi.updateInfo(); return true; } else { return false; } } return false; } /* 19 SP 0 16.9 */ p = Pattern.compile("^(\\d+)\\s+SP\\s+(\\S+)\\s(\\d+\\.\\d+).*"); m = p.matcher(output); if (m.matches()) { /* String counter = m.group(1); // not used */ final String devNrString = m.group(2); final String synced = m.group(3); final Matcher mDev = pDev.matcher(devNrString); String devNr = devNrString; String res = ""; // TODO: unused */ String volumeNr = "0"; if (mDev.matches()) { /* since 8.4 */ devNr = mDev.group(1); res = mDev.group(2); volumeNr = mDev.group(3); } final BlockDevInfo bdi = getBlockDevInfo(devNr, hostName, drbdGraph); if (bdi != null && bdi.getBlockDevice().isDrbd()) { if (Tools.areEqual(bdi.getBlockDevice().getSyncedProgress(), synced)) { return false; } else { bdi.getBlockDevice().setSyncedProgress(synced); bdi.updateInfo(); return true; } } return false; } /* 19 UH 1 split-brain */ p = Pattern.compile("^(\\d+)\\s+UH\\s+(\\S+)\\s([a-z-]+).*"); m = p.matcher(output); if (m.matches()) { /* String counter = m.group(1); // not used */ final String devNrString = m.group(2); final String what = m.group(3); final Matcher mDev = pDev.matcher(devNrString); String devNr = devNrString; String res = ""; // TODO: unused */ String volumeNr = "0"; if (mDev.matches()) { /* since 8.4 */ devNr = mDev.group(1); res = mDev.group(2); volumeNr = mDev.group(3); } Tools.debug(this, "drbd event: " + devNr + " - " + what); if ("split-brain".equals(what)) { final BlockDevInfo bdi = getBlockDevInfo(devNr, hostName, drbdGraph); if (bdi != null && bdi.getBlockDevice().isDrbd()) { if (bdi.getBlockDevice().isSplitBrain()) { return false; } else { bdi.getBlockDevice().setSplitBrain(true); bdi.updateInfo(); return true; } } } return false; } return false; } /** Removes the resource from resources, so that it does not reappear. */ public void removeResource(final String res) { resourceList.remove(res); } /** Removes the volume from hashes, so that it does not reappear. */ public void removeVolume(final String res, final String dev, final String volumeNr) { resourceDeviceMap.remove(res, volumeNr); deviceResourceMap.remove(dev); deviceVolumeMap.remove(dev); } /** * Whether drbd is disabled. If there are unknown sections, that we don't * want to overwrite. */ public boolean isDrbdDisabled() { return (unknownSections || proxyDetected) && !Tools.getConfigData().isAdvancedMode(); } }