/* * This file is part of LCMC written by Rasto Levrinc. * * Copyright (C) 2016, Rastislav Levrinc. * * The LCMC 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. * * The LCMC 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 LCMC; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ package lcmc.host.domain.parser; import java.awt.geom.Point2D; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Provider; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import lcmc.Exceptions; import lcmc.HwEventBus; import lcmc.cluster.domain.Cluster; import lcmc.cluster.service.ssh.ExecCommandConfig; import lcmc.cluster.service.ssh.ExecCommandThread; import lcmc.cluster.ui.ClusterBrowser; import lcmc.common.domain.Application; import lcmc.common.domain.ConvertCmdCallback; import lcmc.common.domain.ExecCallback; import lcmc.common.domain.NewOutputCallback; import lcmc.common.domain.StringValue; import lcmc.common.domain.Value; import lcmc.common.domain.util.Tools; import lcmc.common.ui.CategoryInfo; import lcmc.common.ui.ResourceGraph; import lcmc.common.ui.utils.SwingUtils; import lcmc.drbd.domain.BlockDevice; import lcmc.drbd.domain.DrbdHost; import lcmc.drbd.domain.DrbdXml; import lcmc.drbd.domain.NetInterface; import lcmc.event.HwBlockDevicesChangedEvent; import lcmc.event.HwBlockDevicesDiskSpaceEvent; import lcmc.event.HwBridgesChangedEvent; import lcmc.event.HwDrbdStatusChangedEvent; import lcmc.event.HwFileSystemsChangedEvent; import lcmc.event.HwMountPointsChangedEvent; import lcmc.event.HwNetInterfacesChangedEvent; import lcmc.host.domain.Host; import lcmc.logger.Logger; import lcmc.logger.LoggerFactory; import lcmc.vm.domain.VmsXml; import lombok.RequiredArgsConstructor; import lombok.val; @RequiredArgsConstructor public class HostParser { private final Host host; private final DrbdHost drbdHost; private final HwEventBus hwEventBus; private final Provider<VmsXml> vmsXmlProvider; private final Provider<DrbdXml> drbdXmlProvider; private final SwingUtils swingUtils; private final Application application; private final DistributionDetector distributionDetector; private static final Logger LOG = LoggerFactory.getLogger(Host.class); private static final String NET_INFO_DELIM = "net-info"; private static final String BRIDGE_INFO_DELIM = "bridge-info"; private static final String DISK_INFO_DELIM = "disk-info"; private static final String DISK_SPACE_DELIM = "disk-space"; private static final String VG_INFO_DELIM = "vg-info"; private static final String FILESYSTEMS_INFO_DELIM = "filesystems-info"; private static final String CRYPTO_INFO_DELIM = "crypto-info"; private static final String QEMU_KEYMAPS_INFO_DELIM = "qemu-keymaps-info"; private static final String CPU_MAP_MODEL_INFO_DELIM = "cpu-map-model-info"; private static final String CPU_MAP_VENDOR_INFO_DELIM = "cpu-map-vendor-info"; private static final String MOUNT_POINTS_INFO_DELIM = "mount-points-info"; private static final String GUI_INFO_DELIM = "gui-info"; private static final String INSTALLATION_INFO_DELIM = "installation-info"; private static final String GUI_OPTIONS_INFO_DELIM = "gui-options-info"; private static final String VERSION_INFO_DELIM = "version-info"; private static final String DRBD_PROXY_INFO_DELIM = "drbd-proxy-info"; public static final Pattern BLOCK_DEV_FILE_PATTERN = Pattern.compile("(\\D+)\\d+"); public static final Pattern DRBD_DEV_FILE_PATTERN = Pattern.compile(".*\\/drbd\\d+$"); public static final Pattern USED_DISK_SPACE_PATTERN = Pattern.compile("^(.*) (\\d+)$"); private static final String LOG_COMMANDS_ON_SERVER_OPTION = "--cmd-log"; private static final Collection<String> INFO_TYPES = new HashSet<String>(Arrays.asList(new String[]{NET_INFO_DELIM, BRIDGE_INFO_DELIM, DISK_INFO_DELIM, DISK_SPACE_DELIM, VG_INFO_DELIM, FILESYSTEMS_INFO_DELIM, CRYPTO_INFO_DELIM, QEMU_KEYMAPS_INFO_DELIM, CPU_MAP_MODEL_INFO_DELIM, CPU_MAP_VENDOR_INFO_DELIM, MOUNT_POINTS_INFO_DELIM, GUI_INFO_DELIM, INSTALLATION_INFO_DELIM, GUI_OPTIONS_INFO_DELIM, VERSION_INFO_DELIM, DRBD_PROXY_INFO_DELIM})); private Set<String> availableCryptoModules = Sets.newTreeSet(); private Set<Value> availableQemuKeymaps = new TreeSet<Value>(); private Set<Value> availableCpuMapModels = new TreeSet<Value>(); private Set<Value> availableCpuMapVendors = new TreeSet<Value>(); private Map<String, BlockDevice> drbdBlockDevices = Maps.newLinkedHashMap(); /** Options for GUI drop down lists. */ private Map<String, List<String>> guiOptions = Maps.newHashMap(); private Set<String> drbdResourcesWithProxy = Sets.newHashSet(); private String pacemakerVersion = null; private String openaisVersion = null; private boolean commLayerStopping = false; private boolean commLayerStarting = false; private boolean pacemakerStarting = false; private boolean drbdProxyStarting = false; private boolean corosyncInRc = false; private boolean openaisInRc = false; private boolean corosyncHasInitScript = false; private boolean openaisHasInitScript = false; private boolean corosyncRunning = false; private boolean openaisRunning = false; private boolean corosyncOrOpenaisConfigExists = false; private boolean heartbeatInRc = false; private boolean heartbeatRunning = false; private boolean heartbeatConfigExists = false; private boolean heartbeatHasInitScript = false; private boolean pacemakerInRc = false; private boolean pacemakerRunning = false; private boolean pacemakerHasInitScript = false; /** Pacemaker service version. From version 1, use pacamker init script. */ private int pcmkServiceVersion = -1; private String corosyncVersion = null; private String heartbeatVersion = null; private Boolean corosyncOrHeartbeatRunning = null; private String libvirtVersion = null; private List<BlockDevice> physicalVolumes = new ArrayList<BlockDevice>(); private Map<String, Long> volumeGroups = new LinkedHashMap<String, Long>(); private Map<String, Set<String>> volumeGroupsWithLvs = Maps.newHashMap(); private String heartbeatLibPath = null; private final Lock mInfoTimestampLock = new ReentrantLock(); private final Lock mUpdateVMSlock = new ReentrantLock(); private final Lock mDRBDStatusLock = new ReentrantLock(); private ExecCommandThread serverStatusThread = null; private final CountDownLatch waitForServerStatusLatch = new CountDownLatch(1); /** Time stamp hash. */ private final Map<String, Double> infoTimestamp = Maps.newHashMap(); private boolean drbdStatusOk = false; private static final String TOKEN_DISK_ID = "disk-id"; private static final String TOKEN_UUID = "uuid"; private static final String TOKEN_SIZE = "size"; private static final String TOKEN_MP = "mp"; private static final String TOKEN_FS = "fs"; private static final String TOKEN_VG = "vg"; private static final String TOKEN_LV = "lv"; private static final String TOKEN_PV = "pv"; private static final int HW_INFO_TIMEOUT = 40000; /** List of positions of the services. * Question is this: the saved positions can be different on different * hosts, but only one can be used in the crm graph. * Only one will be used and by next save the problem solves itself. */ private final Map<String, Point2D> servicePositions = Maps.newHashMap(); public void parseHostInfo(final String ans) { LOG.debug1("parseHostInfo: updating host info: " + host.getName()); final String[] lines = ans.split("\\r?\\n"); final List<String> versionLines = Lists.newArrayList(); final Map<String, BlockDevice> newBlockDevices = Maps.newLinkedHashMap(); final Map<String, BlockDevice> newDrbdBlockDevices = Maps.newLinkedHashMap(); final List<NetInterface> newNetInterfaces = Lists.newArrayList(); final List<Value> newBridges = Lists.newArrayList(); final Map<String, Long> newVolumeGroups = Maps.newLinkedHashMap(); final Map<String, Set<String>> newVolumeGroupsLVS = Maps.newHashMap(); final List<BlockDevice> newPhysicalVolumes = Lists.newArrayList(); final Set<String> fileSystems = Sets.newTreeSet(); final Set<String> newCryptoModules = Sets.newTreeSet(); final Set<Value> newQemuKeymaps = new TreeSet<Value>(); final Set<Value> newCpuMapModels = new TreeSet<Value>(); final Set<Value> newCpuMapVendors = new TreeSet<Value>(); final Set<String> mountPoints = Sets.newTreeSet(); final Map<String, List<String>> newGuiOptions = Maps.newHashMap(); final Set<String> newDrbdResProxy = Sets.newHashSet(); final Collection<String> changedTypes = Sets.newHashSet(); final Map<String, String> diskSpaces = Maps.newHashMap(); mountPoints.add("/mnt/"); String guiOptionName = null; String type = ""; for (final String line : lines) { if (line.indexOf("ERROR:") == 0) { break; } else if (line.indexOf("WARNING:") == 0) { continue; } if (INFO_TYPES.contains(line)) { type = line; changedTypes.add(type); continue; } if ("net-info".equals(type)) { try { final NetInterface netInterface = new NetInterface(line); if (netInterface.getIp() != null && !"".equals(netInterface.getIp())) { newNetInterfaces.add(netInterface); } } catch (final UnknownHostException e) { LOG.appWarning("parseHostInfo: cannot parse: net-info: " + line); } } else if (BRIDGE_INFO_DELIM.equals(type)) { newBridges.add(new StringValue(line)); } else if (DISK_INFO_DELIM.equals(type)) { final Optional<BlockDevice> blockDevice = createBlockDevice(line); if (!blockDevice.isPresent()) { continue; } final String bdName = blockDevice.get().getName(); if (bdName != null) { final Matcher drbdM = DRBD_DEV_FILE_PATTERN.matcher(bdName); if (drbdM.matches()) { if (drbdBlockDevices.containsKey(bdName)) { drbdBlockDevices.get(bdName).updateFrom(blockDevice.get()); } else { newDrbdBlockDevices.put(bdName, blockDevice.get()); } } else { newBlockDevices.put(bdName, blockDevice.get()); if (blockDevice.get().getVolumeGroup() == null && bdName.length() > 5 && bdName.indexOf('/', 5) < 0) { final Matcher m = BLOCK_DEV_FILE_PATTERN.matcher(bdName); if (m.matches()) { newBlockDevices.remove(m.group(1)); } } } } final String vg = blockDevice.get().getVolumeGroup(); if (vg != null) { Set<String> logicalVolumes = newVolumeGroupsLVS.get(vg); if (logicalVolumes == null) { logicalVolumes = new HashSet<String>(); newVolumeGroupsLVS.put(vg, logicalVolumes); } final String lv = blockDevice.get().getLogicalVolume(); if (lv != null) { logicalVolumes.add(lv); } } if (blockDevice.get().isPhysicalVolume()) { newPhysicalVolumes.add(blockDevice.get()); } } else if (DISK_SPACE_DELIM.equals(type)) { final Matcher dsM = USED_DISK_SPACE_PATTERN.matcher(line); if (dsM.matches()) { final String bdName = dsM.group(1); final String used = dsM.group(2); diskSpaces.put(bdName, used); } } else if ("vg-info".equals(type)) { final String[] vgi = line.split("\\s+"); if (vgi.length == 2) { newVolumeGroups.put(vgi[0], Long.parseLong(vgi[1])); } else { LOG.appWarning("parseHostInfo: could not parse volume info: " + line); } } else if ("filesystems-info".equals(type)) { fileSystems.add(line); } else if ("crypto-info".equals(type)) { newCryptoModules.add(line); } else if ("qemu-keymaps-info".equals(type)) { newQemuKeymaps.add(new StringValue(line)); } else if ("cpu-map-model-info".equals(type)) { newCpuMapModels.add(new StringValue(line)); } else if ("cpu-map-vendor-info".equals(type)) { newCpuMapVendors.add(new StringValue(line)); } else if ("mount-points-info".equals(type)) { mountPoints.add(line); } else if ("gui-info".equals(type)) { parseGuiInfo(line); } else if ("installation-info".equals(type)) { parseInstallationInfo(line); } else if ("gui-options-info".equals(type)) { guiOptionName = parseGuiOptionsInfo(line, guiOptionName, newGuiOptions); } else if (VERSION_INFO_DELIM.equals(type)) { versionLines.add(line); } else if ("drbd-proxy-info".equals(type)) { /* res-other.host-this.host */ final Cluster cluster = host.getCluster(); if (cluster != null) { String res = null; if (line.startsWith("up:")) { for (final Host otherHost : cluster.getProxyHosts()) { if (otherHost == host) { continue; } final String hostsPart = '-' + otherHost.getName() + '-' + host.getName(); final int i = line.indexOf(hostsPart); if (i > 0) { res = line.substring(3, i); break; } } } if (res == null) { LOG.appWarning("parseHostInfo: could not parse proxy line: " + line); } else { newDrbdResProxy.add(res); } } } } LOG.debug1("parseHostInfo: " + host.getName() + ", pacemaker: " + pacemakerVersion + ", corosync: " + corosyncVersion + ", heartbeat: " + heartbeatVersion + ", drbd: " + drbdHost.getDrbdUtilVersion() + ", drbd module: " + drbdHost.getDrbdModuleVersion()); if (changedTypes.contains(NET_INFO_DELIM)) { hwEventBus.post(new HwNetInterfacesChangedEvent(host, newNetInterfaces)); } if (changedTypes.contains(BRIDGE_INFO_DELIM)) { hwEventBus.post(new HwBridgesChangedEvent(host, newBridges)); } if (changedTypes.contains(DISK_INFO_DELIM)) { drbdBlockDevices = newDrbdBlockDevices; physicalVolumes = newPhysicalVolumes; volumeGroupsWithLvs = newVolumeGroupsLVS; } if (changedTypes.contains(DISK_SPACE_DELIM)) { hwEventBus.post(new HwBlockDevicesDiskSpaceEvent(host, diskSpaces)); } if (changedTypes.contains(VG_INFO_DELIM)) { volumeGroups = newVolumeGroups; } if (changedTypes.contains(FILESYSTEMS_INFO_DELIM)) { hwEventBus.post(new HwFileSystemsChangedEvent(host, fileSystems)); } if (changedTypes.contains(CRYPTO_INFO_DELIM)) { availableCryptoModules = newCryptoModules; } if (changedTypes.contains(QEMU_KEYMAPS_INFO_DELIM)) { availableQemuKeymaps = newQemuKeymaps; } if (changedTypes.contains(CPU_MAP_MODEL_INFO_DELIM)) { availableCpuMapModels = newCpuMapModels; } if (changedTypes.contains(CPU_MAP_VENDOR_INFO_DELIM)) { availableCpuMapVendors = newCpuMapVendors; } if (changedTypes.contains(MOUNT_POINTS_INFO_DELIM)) { hwEventBus.post(new HwMountPointsChangedEvent(host, mountPoints)); } if (changedTypes.contains(VERSION_INFO_DELIM)) { distributionDetector.detect(ImmutableList.copyOf(versionLines)); } if (changedTypes.contains(GUI_OPTIONS_INFO_DELIM)) { guiOptions = newGuiOptions; } if (changedTypes.contains(DRBD_PROXY_INFO_DELIM)) { drbdResourcesWithProxy = newDrbdResProxy; } if (changedTypes.contains(DISK_INFO_DELIM) || changedTypes.contains(VG_INFO_DELIM)) { hwEventBus.post(new HwBlockDevicesChangedEvent(host, newBlockDevices.values())); } } public String getArch() { return distributionDetector.getArch(); } public String getDetectedInfo() { return distributionDetector.getDetectedInfo(); } public String getDistFromDistVersion(final String dV) { return distributionDetector.getDistFromDistVersion(dV); } public String getKernelName() { return distributionDetector.getKernelName(); } public String getKernelVersion() { return distributionDetector.getKernelVersion(); } public String getDetectedKernelVersion() { return distributionDetector.getDetectedKernelVersion(); } public String getDistCommand(final String text, final ConvertCmdCallback convertCmdCallback, final boolean inBash, final boolean inSudo) { return distributionDetector.getDistCommand(text, convertCmdCallback, inBash, inSudo); } /** Parses the gui info, with drbd and heartbeat graph positions. */ private void parseGuiInfo(final String line) { final String[] tokens = line.split(";"); String id = null; String x = null; String y = null; for (final String token : tokens) { final String[] r = token.split("="); if (r.length == 2) { if (r[0].equals("hb") || r[0].equals("dr")) { id = token; } else if (r[0].equals("x")) { x = r[1]; } else if (r[0].equals("y")) { y = r[1]; } } } if (id != null && x != null && y != null) { servicePositions.put(id, new Point2D.Double(Double.parseDouble(x), Double.parseDouble(y))); } } /** Parses the gui options info. */ public String parseGuiOptionsInfo(final String line, final String guiOptionName, final Map<String, List<String>> goptions) { if (line.length() > 2 && line.substring(0, 2).equals("o:")) { final String op = line.substring(2); goptions.put(op, new ArrayList<String>()); return op; } if (guiOptionName != null) { final List<String> options = goptions.get(guiOptionName); if (options != null) { options.add(line); } } return guiOptionName; } public void parseInstallationInfo(final String line) { final String[] tokens = line.split(":|\\s+"); if (tokens.length < 2) { return; } if ("pm".equals(tokens[0])) { if (tokens.length == 2) { pacemakerVersion = tokens[1].trim(); } else { pacemakerVersion = null; } } else if ("cs".equals(tokens[0])) { if (tokens.length == 2) { corosyncVersion = tokens[1].trim(); } else { corosyncVersion = null; } } else if ("ais".equals(tokens[0])) { if (tokens.length == 2) { openaisVersion = tokens[1].trim(); } else { openaisVersion = null; } } else if ("ais-rc".equals(tokens[0])) { if (tokens.length == 2) { openaisInRc = "on".equals(tokens[1].trim()); } else { openaisInRc = false; } } else if ("cs-rc".equals(tokens[0])) { if (tokens.length == 2) { corosyncInRc = "on".equals(tokens[1].trim()); } else { corosyncInRc = false; } } else if ("cs-ais-conf".equals(tokens[0])) { if (tokens.length == 2) { corosyncOrOpenaisConfigExists = "on".equals(tokens[1].trim()); } else { corosyncOrOpenaisConfigExists = false; } } else if ("cs-running".equals(tokens[0])) { if (tokens.length == 2) { corosyncRunning = "on".equals(tokens[1].trim()); } else { corosyncRunning = false; } } else if ("ais-running".equals(tokens[0])) { if (tokens.length == 2) { openaisRunning = "on".equals(tokens[1].trim()); commLayerStarting = false; pacemakerStarting = false; } else { openaisRunning = false; } } else if ("cs-init".equals(tokens[0])) { if (tokens.length == 2) { corosyncHasInitScript = "on".equals(tokens[1].trim()); } else { corosyncHasInitScript = false; } } else if ("ais-init".equals(tokens[0])) { if (tokens.length == 2) { openaisHasInitScript = "on".equals(tokens[1].trim()); } else { openaisHasInitScript = false; } } else if ("hb".equals(tokens[0])) { if (tokens.length == 2) { heartbeatVersion = tokens[1].trim(); } else { heartbeatVersion = null; } } else if ("hb-init".equals(tokens[0])) { if (tokens.length == 2) { heartbeatHasInitScript = "on".equals(tokens[1].trim()); } else { heartbeatHasInitScript = false; } } else if ("hb-rc".equals(tokens[0])) { if (tokens.length == 2) { heartbeatInRc = "on".equals(tokens[1].trim()); } else { heartbeatInRc = false; } } else if ("hb-conf".equals(tokens[0])) { if (tokens.length == 2) { heartbeatConfigExists = "on".equals(tokens[1].trim()); } else { heartbeatConfigExists = false; } } else if ("hb-running".equals(tokens[0])) { if (tokens.length == 2) { heartbeatRunning = "on".equals(tokens[1].trim()); } else { heartbeatRunning = false; } } else if ("pcmk-rc".equals(tokens[0])) { if (tokens.length == 2) { pacemakerInRc = "on".equals(tokens[1].trim()); } else { pacemakerInRc = false; } } else if ("pcmk-running".equals(tokens[0])) { if (tokens.length == 2) { pacemakerRunning = "on".equals(tokens[1].trim()); } else { pacemakerRunning = false; } } else if ("drbdp-running".equals(tokens[0])) { if (tokens.length == 2) { drbdHost.setDrbdProxyRunning("on".equals(tokens[1].trim())); } else { drbdHost.setDrbdProxyRunning(false); } } else if ("pcmk-init".equals(tokens[0])) { if (tokens.length == 2) { pacemakerHasInitScript = "on".equals(tokens[1].trim()); } else { pacemakerHasInitScript = false; } } else if ("pcmk-svc-ver".equals(tokens[0])) { if (tokens.length == 2) { try { pcmkServiceVersion = Integer.parseInt(tokens[1].trim()); } catch (final NumberFormatException e) { pcmkServiceVersion = -1; } } } else if ("drbd-loaded".equals(tokens[0])) { if (tokens.length == 2) { drbdHost.setDrbdLoaded("on".equals(tokens[1].trim())); } else { drbdHost.setDrbdLoaded(false); } } else if ("hb-lib-path".equals(tokens[0])) { if (tokens.length == 2) { heartbeatLibPath = tokens[1].trim(); } else { heartbeatLibPath = null; } } else if ("hn".equals(tokens[0])) { if (tokens.length == 2) { host.setHostname(tokens[1].trim()); } else { host.setHostname(null); } host.setName(host.getHostname()); } else if ("drbd".equals(tokens[0])) { if (tokens.length == 2) { drbdHost.setDrbdUtilVersion(tokens[1].trim()); } else { drbdHost.setDrbdUtilVersion(null); } } else if ("drbd-mod".equals(tokens[0])) { if (tokens.length == 2) { drbdHost.setDrbdModuleVersion(tokens[1].trim()); } else { drbdHost.setDrbdModuleVersion(null); } } corosyncOrHeartbeatRunning = heartbeatRunning || corosyncRunning || openaisRunning; if (commLayerStarting && (corosyncRunning || openaisRunning || heartbeatRunning)) { commLayerStarting = false; } if (pacemakerStarting && pacemakerRunning) { pacemakerStarting = false; } if (drbdProxyStarting && drbdHost.isDrbdProxyRunning()) { drbdProxyStarting = false; } if (commLayerStopping && !corosyncRunning && !openaisRunning && !heartbeatRunning) { commLayerStopping = false; } } public Set<String> getAvailableCryptoModules() { return availableCryptoModules; } public Set<Value> getAvailableQemuKeymaps() { return availableQemuKeymaps; } public Set<Value> getCPUMapModels() { return availableCpuMapModels; } public Set<Value> getCPUMapVendors() { return availableCpuMapVendors; } public String getHeartbeatLibPath() { if (heartbeatLibPath != null) { return heartbeatLibPath; } val arch = distributionDetector.getArch(); if ("".equals(arch)) { LOG.appWarning("getHeartbeatLibPath: called to soon: unknown arch"); } else if ("x86_64".equals(arch) || "amd64".equals(arch)) { return "/usr/lib64/heartbeat"; } return "/usr/lib/heartbeat"; } /** Gets distribution, e.g., debian. */ public String getDistributionName() { return distributionDetector.getDistributionName(); } public String getDistributionVersion() { return distributionDetector.getDistributionVersion(); } public String getDistributionVersionString() { return distributionDetector.getDistributionVersionString(); } /** * Converts command string to real command for a distribution, specifying * the convert command callback. */ public String getDistCommand(final String commandString, final ConvertCmdCallback convertCmdCallback) { return getDistCommand(commandString, convertCmdCallback, false, /* in bash */ false); /* sudo */ } /** Converts a string that is specific to the distribution distribution. */ public String getDistString(final String commandString) { return distributionDetector.getDistString(commandString); } /** * Gets list of strings that are specific to the distribution * distribution. */ public List<String> getDistStrings(final String commandString) { return distributionDetector.getDistStrings(commandString); } /** * Converts command string to real command for a distribution, specifying * what-with-what hash. */ public String getDistCommand(final String commandString, final Map<String, String> replaceHash) { return getDistCommand( commandString, command -> { for (final String tag : replaceHash.keySet()) { if (tag != null && command.contains(tag)) { String s = replaceHash.get(tag); if (s == null) { s = ""; } command = command.replaceAll(tag, s); } } return command; }, false, /* in bash */ false); /* sudo */ } /** Gets and stores info about the host. */ public void getAllInfo() { host.execCommand(new ExecCommandConfig().commandString("GetHostAllInfo") .execCallback(new ExecCallback() { @Override public void done(final String ans) { parseHostInfo(ans); host.setLoadingDone(); } @Override public void doneError(final String ans, final int exitCode) { host.setLoadingError(); } }) .sshCommandTimeout(HW_INFO_TIMEOUT) .silentCommand() .silentOutput() .silentCommand()).block(); } /** Gets and stores hardware info about the host. */ public void getHWInfo(final boolean updateLVM) { getHWInfo(new CategoryInfo[]{}, new ResourceGraph[]{}, updateLVM); } /** Gets and stores hardware info about the host. */ public void getHWInfo(final CategoryInfo[] infosToUpdate, final ResourceGraph[] graphs, final boolean updateLVM) { final String cmd; if (updateLVM) { cmd = "GetHostHWInfoLVM"; } else { cmd = "GetHostHWInfo"; } host.execCommand(new ExecCommandConfig().commandString(cmd) .execCallback(new ExecCallback() { @Override public void done(final String ans) { parseHostInfo(ans); for (final CategoryInfo ci : infosToUpdate) { ci.updateTable(CategoryInfo.MAIN_TABLE); } for (final ResourceGraph g : graphs) { if (g != null) { g.repaint(); } } host.setLoadingDone(); } @Override public void doneError(final String ans, final int exitCode) { host.setLoadingError(); host.getSSH().forceReconnect(); } }) .sshCommandTimeout(HW_INFO_TIMEOUT) .silentCommand() .silentOutput()).block(); } /** Gets and stores hardware info about the host. */ public void startHWInfoDaemon(final CategoryInfo[] infosToUpdate, final ResourceGraph[] graphs) { LOG.debug1("startHWInfoDaemon: " + host.getName()); serverStatusThread = host.getSSH().execCommand(new ExecCommandConfig() .commandString("HostHWInfoDaemon") .inBash(false) .inSudo(false) .execCallback(new ExecCallback() { @Override public void done(final String ans) { parseHostInfo(ans); for (final CategoryInfo ci : infosToUpdate) { ci.updateTable(CategoryInfo.MAIN_TABLE); } for (final ResourceGraph g : graphs) { if (g != null) { g.repaint(); } } if (getWaitForServerStatusLatch()) { final ClusterBrowser cb = host.getBrowser().getClusterBrowser(); cb.updateServerStatus(host); } host.setLoadingDone(); } @Override public void doneError(final String ans, final int exitCode) { if (getWaitForServerStatusLatch()) { final ClusterBrowser cb = host.getBrowser().getClusterBrowser(); cb.updateServerStatus(host); } host.setLoadingError(); } }) .newOutputCallback(new NewOutputCallback() { private final StringBuffer outputBuffer = new StringBuffer(300); @Override public void output(final String output) { outputBuffer.append(output); final ClusterBrowser cb = host.getBrowser().getClusterBrowser(); String hw, vm, drbdConfig; String hwUpdate = null; String vmUpdate = null; String drbdUpdate = null; do { hw = getOutput("hw", outputBuffer); if (hw != null) { hwUpdate = hw; } vm = getOutput("vm", outputBuffer); if (vmStatusTryLock()) { if (vm != null) { vmUpdate = vm; } vmStatusUnlock(); } drbdStatusLock(); drbdConfig = getOutput("drbd", outputBuffer); if (drbdConfig != null) { drbdUpdate = drbdConfig; } drbdStatusUnlock(); } while (hw != null || vm != null || drbdConfig != null); Tools.chomp(outputBuffer); if (hwUpdate != null) { parseHostInfo(hwUpdate); for (final ResourceGraph g : graphs) { if (g != null) { g.repaint(); } } } if (vmUpdate != null) { final VmsXml newVmsXml = vmsXmlProvider.get(); newVmsXml.init(host); if (newVmsXml.parseXml(vmUpdate)) { cb.vmsXmlPut(host, newVmsXml); cb.updateVms(); } } if (drbdUpdate != null) { final DrbdXml dxml = drbdXmlProvider.get(); dxml.init(host.getCluster().getHostsArray(), cb.getHostDrbdParameters()); dxml.update(drbdUpdate); cb.setDrbdXml(dxml); swingUtils.invokeLater(new Runnable() { @Override public void run() { host.getBrowser().getClusterBrowser().getGlobalInfo().setParameters(); cb.updateDrbdResources(); } }); } if (drbdUpdate != null || vmUpdate != null) { cb.updateHWInfo(host, !Host.UPDATE_LVM); } if (drbdUpdate != null) { cb.updateServerStatus(host); } if (getWaitForServerStatusLatch()) { cb.updateServerStatus(host); } host.setLoadingDone(); } }) .silentCommand() .silentOutput() .sshCommandTimeout(HW_INFO_TIMEOUT)).block(); } public String getOutput(final String type, final StringBuffer buffer) { final String infoStart = "--" + type + "-info-start--"; final String infoEnd = "--" + type + "-info-end--"; final int infoStartLength = infoStart.length(); final int infoEndLength = infoEnd.length(); final int s = buffer.indexOf(infoStart); final int s2 = buffer.indexOf("\r\n", s); final int e = buffer.indexOf(infoEnd, s); String out = null; if (s > -1 && s < s2 && s2 <= e) { Double timestamp = null; final String ts = buffer.substring(s + infoStartLength, s2); try { timestamp = Double.parseDouble(ts); } catch (final NumberFormatException nfe) { LOG.debug("getOutput: could not parse: " + ts + ' ' + nfe); } mInfoTimestampLock.lock(); if (timestamp != null && (!infoTimestamp.containsKey(type) || timestamp >= infoTimestamp.get(type))) { infoTimestamp.put(type, timestamp); mInfoTimestampLock.unlock(); out = buffer.substring(s2 + 2, e); } else { mInfoTimestampLock.unlock(); } buffer.delete(0, e + infoEndLength + 2); } return out; } public void vmStatusLock() { mUpdateVMSlock.lock(); } public boolean vmStatusTryLock() { return mUpdateVMSlock.tryLock(); } public void vmStatusUnlock() { mUpdateVMSlock.unlock(); } public boolean drbdStatusTryLock() { return mDRBDStatusLock.tryLock(); } public void drbdStatusLock() { mDRBDStatusLock.lock(); } public void drbdStatusUnlock() { mDRBDStatusLock.unlock(); } /** The latch is set when the server status is run for the first time. */ public void serverStatusLatchDone() { waitForServerStatusLatch.countDown(); } /** Returns true if latch is set. */ public boolean getWaitForServerStatusLatch() { return waitForServerStatusLatch.getCount() == 1; } /** Stops server (hw) status background process. */ public void stopServerStatus() { final ExecCommandThread sst = serverStatusThread; if (sst == null) { LOG.appWarning("trying to stop stopped server status"); return; } sst.cancelTheSession(); serverStatusThread = null; } public long getFreeInVolumeGroup(final String volumeGroup) { final Long f = volumeGroups.get(volumeGroup); if (f == null) { return 0; } return f; } public Set<String> getVolumeGroupNames() { return volumeGroups.keySet(); } public Boolean getCorosyncOrHeartbeatRunning() { return corosyncOrHeartbeatRunning; } public void setCorosyncOrHeartbeatRunning(final Boolean corosyncOrHeartbeatRunning) { this.corosyncOrHeartbeatRunning = corosyncOrHeartbeatRunning; } public boolean isCommLayerStopping() { return commLayerStopping; } public void setCommLayerStopping(final boolean commLayerStopping) { this.commLayerStopping = commLayerStopping; } public boolean isCommLayerStarting() { return commLayerStarting; } public void setCommLayerStarting(final boolean commLayerStarting) { this.commLayerStarting = commLayerStarting; } public boolean isPacemakerStarting() { return pacemakerStarting; } public void setPacemakerStarting(final boolean pacemakerStarting) { this.pacemakerStarting = pacemakerStarting; } public boolean isDrbdProxyStarting() { return drbdProxyStarting; } public void setDrbdProxyStarting(final boolean drbdProxyStarting) { this.drbdProxyStarting = drbdProxyStarting; } public boolean isPcmkStartedByCorosync() { return pcmkServiceVersion == 0; } public void setLibvirtVersion(final String libvirtVersion) { this.libvirtVersion = libvirtVersion; } public String getLibvirtVersion() { return libvirtVersion; } public Set<String> getLogicalVolumesFromVolumeGroup(final String vg) { return volumeGroupsWithLvs.get(vg); } public Set<String> getAllLogicalVolumes() { final Set<String> allLVS = new LinkedHashSet<String>(); for (final String vg : volumeGroups.keySet()) { final Set<String> lvs = volumeGroupsWithLvs.get(vg); if (lvs != null) { allLVS.addAll(lvs); } } return allLVS; } /** Returns whether DRBD has volume feature. */ public boolean hasVolumes() { try { return Tools.compareVersions(drbdHost.getDrbdUtilVersion(), "8.4") >= 0; } catch (final Exceptions.IllegalVersionException e) { LOG.appWarning("hasVolumes: " + e.getMessage(), e); } return true; } public Iterable<BlockDevice> getPhysicalVolumes() { return physicalVolumes; } public void setHeartbeatVersion(final String heartbeatVersion) { this.heartbeatVersion = heartbeatVersion; } public void setCorosyncVersion(final String corosyncVersion) { this.corosyncVersion = corosyncVersion; } public void setPacemakerVersion(final String pacemakerVersion) { this.pacemakerVersion = pacemakerVersion; } public void setOpenaisVersion(final String openaisVersion) { this.openaisVersion = openaisVersion; } public String getPacemakerVersion() { return pacemakerVersion; } public String getCorosyncVersion() { return corosyncVersion; } public boolean isCorosyncInstalled() { return corosyncVersion != null; } public boolean isOpenaisWrapper() { return "wrapper".equals(openaisVersion); } public String getOpenaisVersion() { return openaisVersion; } public String getHeartbeatVersion() { return heartbeatVersion; } /** Waits for the server status latch. */ public void waitForServerStatusLatch() { try { waitForServerStatusLatch.await(); } catch (final InterruptedException ignored) { Thread.currentThread().interrupt(); } } public boolean isCorosyncInRc() { return corosyncInRc; } public boolean isOpenaisInRc() { return openaisInRc; } public boolean isPacemakerInRc() { return pacemakerInRc; } public boolean hasHeartbeatInitScript() { return heartbeatHasInitScript; } public boolean hasCorosyncInitScript() { return corosyncHasInitScript; } public boolean hasOpenaisInitScript() { return openaisHasInitScript; } public boolean hasPacemakerInitScript() { return pacemakerHasInitScript; } public boolean isCorosyncRunning() { return corosyncRunning; } public boolean isPacemakerRunning() { return pacemakerRunning; } public boolean isOpenaisRunning() { return openaisRunning; } public boolean corosyncOrOpenaisConfigExists() { return corosyncOrOpenaisConfigExists; } public boolean isHeartbeatInRc() { return heartbeatInRc; } public boolean isHeartbeatRunning() { return heartbeatRunning; } public boolean heartbeatConfigExists() { return heartbeatConfigExists; } public BlockDevice getDrbdBlockDevice(final String device) { return drbdBlockDevices.get(device); } public Iterable<BlockDevice> getDrbdBlockDevices() { return drbdBlockDevices.values(); } public Iterable<BlockDevice> getPhysicalVolumes(final String vg) { final Collection<BlockDevice> bds = new ArrayList<BlockDevice>(); if (vg == null) { return bds; } for (final BlockDevice b : physicalVolumes) { if (vg.equals(b.getVgOnPhysicalVolume())) { bds.add(b); } } return bds; } public Iterable<String> getGuiOptions(final String name) { final List<String> opts = guiOptions.get(name); if (opts == null) { return new ArrayList<String>(); } return new ArrayList<String>(guiOptions.get(name)); } public boolean isDrbdProxyUp(final String drbdResource) { return drbdResourcesWithProxy.contains(drbdResource); } public String getLxcLibPath() { return getDistString("libvirt.lxc.libpath"); } /** Returns xen lib path. */ public String getXenLibPath() { return getDistString("libvirt.xen.libpath"); } public Point2D getGraphPosition(final String id) { return servicePositions.get(id); } public void resetGraphPosition(final String id) { servicePositions.remove(id); } /** * Replaces variables in command. * * Following variables are defined: * * \@USER\@ user for download area * \@PASSWORD\@ password for download area * \@KERNELVERSION\@ version of the kernel * \@DRBDVERSION\@ version of drbd, that will be installed * \@DISTRIBUTION\@ distribution for which the drbd will be installed. * * @param command * command in which the variables will be replaced * * @return command with replaced variables */ public String replaceVars(final String command) { return replaceVars(command, false); } /** * replaces variables in command. For output to the user set hidePassword * to true, so that variables like passwords are not shown to the user. * This functions should not be used to really sensitive passwords, since * it is not secure. * * following variables are defined: * * \@USER\@ user for download area * \@PASSWORD\@ password for download area * \@KERNELVERSION\@ version of the kernel * \@DRBDVERSION\@ version of drbd, that will be installed * \@DISTRIBUTION\@ distribution for which the drbd will be installed. * * @param command * command in which the variables will be replaced * * @param hidePassword * if set to true all vars will be replaced except of sensitive * ones. * * @return command with replaced variables */ public String replaceVars(String command, final boolean hidePassword) { if (command.contains("@USER@")) { command = command.replaceAll("@USER@", application.getDownloadUser()); } if (command.indexOf("@PASSWORD@") > -1) { if (hidePassword) { command = command.replaceAll("@PASSWORD@", "*****"); } else { command = command.replaceAll("@PASSWORD@", application.getDownloadPassword()); } } String supportDir = "support"; if (application.isStagingDrbd()) { supportDir = "support/staging"; } if (distributionDetector.getKernelVersion() != null && command.contains("@KERNELVERSIONDIR@")) { command = command.replaceAll("@KERNELVERSIONDIR@", distributionDetector.getKernelVersion()); } if (distributionDetector.getDistributionVersion() != null && command.contains("@DISTRIBUTION@")) { command = command.replaceAll("@DISTRIBUTION@", distributionDetector.getDistributionVersion()); } if (distributionDetector.getArch() != null && command.contains("@ARCH@")) { command = command.replaceAll("@ARCH@", distributionDetector.getArch()); } if (command.contains("@SUPPORTDIR@")) { command = command.replaceAll("@SUPPORTDIR@", supportDir); } if (command.contains("@DRBDDIR@")) { final String drbdDir = "drbd"; command = command.replaceAll("@DRBDDIR@", drbdDir); } if (command.contains("@GUI-HELPER@")) { final StringBuilder helperProg = new StringBuilder("/usr/local/bin/lcmc-gui-helper-"); helperProg.append(Tools.getRelease()); if (application.isCmdLog()) { helperProg.append(' '); helperProg.append(LOG_COMMANDS_ON_SERVER_OPTION); } command = command.replaceAll("@GUI-HELPER@", helperProg.toString()); } if (command.contains("@GUI-HELPER-PROG@")) { command = command.replaceAll("@GUI-HELPER-PROG@", "/usr/local/bin/lcmc-gui-helper-" + Tools.getRelease()); } return command; } public void setDrbdStatusOk(final boolean drbdStatusOk) { this.drbdStatusOk = drbdStatusOk; hwEventBus.post(new HwDrbdStatusChangedEvent(host, drbdStatusOk)); } public boolean isDrbdStatusOk() { return drbdStatusOk; } private Optional<BlockDevice> createBlockDevice(final String line) { final Pattern p = Pattern.compile("([^:]+):(.*)"); final String[] cols = line.split(" "); if (cols.length < 2) { LOG.appWarning("update: cannot parse block device line: " + line); return Optional.absent(); } else { final Collection<String> diskIds = new HashSet<String>(); final String device = cols[0]; final Map<String, String> tokens = Maps.newHashMap(); for (int i = 1; i < cols.length; i++) { final Matcher m = p.matcher(cols[i]); if (m.matches()) { if (TOKEN_DISK_ID.equals(m.group(1))) { diskIds.add(m.group(2)); } else { tokens.put(m.group(1), m.group(2)); } } else { LOG.appWarning("update: could not parse: " + line); } } final BlockDevice blockDevice = new BlockDevice(host, device); blockDevice.setDiskUuid(tokens.get(TOKEN_UUID)); blockDevice.setBlockSize(tokens.get(TOKEN_SIZE)); blockDevice.setMountedOn(tokens.get(TOKEN_MP)); blockDevice.setFsType(tokens.get(TOKEN_FS)); blockDevice.setVolumeGroup(tokens.get(TOKEN_VG)); blockDevice.setLogicalVolume(tokens.get(TOKEN_LV)); blockDevice.setVgOnPhysicalVolume(tokens.get(TOKEN_PV)); blockDevice.setDiskIds(diskIds); return Optional.of(blockDevice); } } }