lcmc.cluster.ui.ClusterBrowser.java Source code

Java tutorial

Introduction

Here is the source code for lcmc.cluster.ui.ClusterBrowser.java

Source

/*
 * This file is part of DRBD Management Console by Rasto Levrinc,
 * LINBIT HA-Solutions GmbH
 *
 * Copyright (C) 2009, 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.cluster.ui;

import com.google.common.collect.Table;
import com.google.common.eventbus.Subscribe;
import lcmc.ClusterEventBus;
import lcmc.cluster.domain.Cluster;
import lcmc.cluster.domain.Network;
import lcmc.cluster.service.NetworkService;
import lcmc.cluster.ui.network.NetworkFactory;
import lcmc.cluster.ui.network.NetworkPresenter;
import lcmc.common.domain.Application;
import lcmc.common.domain.ExecCallback;
import lcmc.common.domain.NewOutputCallback;
import lcmc.common.domain.util.Tools;
import lcmc.common.ui.Access;
import lcmc.common.ui.Browser;
import lcmc.common.ui.CallbackAction;
import lcmc.common.ui.CategoryInfo;
import lcmc.common.ui.Info;
import lcmc.common.ui.ResourceGraph;
import lcmc.common.ui.main.MainData;
import lcmc.common.ui.main.ProgressIndicator;
import lcmc.common.ui.treemenu.TreeMenuController;
import lcmc.common.ui.utils.ButtonCallback;
import lcmc.common.ui.utils.ComponentWithTest;
import lcmc.common.ui.utils.SwingUtils;
import lcmc.crm.domain.ClusterStatus;
import lcmc.crm.domain.CrmXml;
import lcmc.crm.domain.PtestData;
import lcmc.crm.domain.ResourceAgent;
import lcmc.crm.domain.Service;
import lcmc.crm.service.CRM;
import lcmc.crm.service.Heartbeat;
import lcmc.crm.ui.CrmGraph;
import lcmc.crm.ui.resource.AvailableServiceInfo;
import lcmc.crm.ui.resource.AvailableServicesInfo;
import lcmc.crm.ui.resource.CRMInfo;
import lcmc.crm.ui.resource.GroupInfo;
import lcmc.crm.ui.resource.HbConnectionInfo;
import lcmc.crm.ui.resource.ResourceAgentClassInfo;
import lcmc.crm.ui.resource.update.ResourceUpdater;
import lcmc.crm.ui.resource.RscDefaultsInfo;
import lcmc.crm.ui.resource.ServiceInfo;
import lcmc.crm.ui.resource.ServicesInfo;
import lcmc.drbd.domain.DRBDtestData;
import lcmc.drbd.domain.DrbdXml;
import lcmc.drbd.service.DRBD;
import lcmc.drbd.ui.DrbdGraph;
import lcmc.drbd.ui.resource.BlockDevInfo;
import lcmc.drbd.ui.resource.GlobalInfo;
import lcmc.drbd.ui.resource.ResourceInfo;
import lcmc.drbd.ui.resource.VolumeInfo;
import lcmc.event.NetworkChangedEvent;
import lcmc.host.domain.Host;
import lcmc.host.ui.ClusterHostsInfo;
import lcmc.host.ui.HostBrowser;
import lcmc.logger.Logger;
import lcmc.logger.LoggerFactory;
import lcmc.vm.domain.VmsXml;
import lcmc.vm.ui.resource.DomainInfo;
import lcmc.vm.ui.resource.HardwareInfo;
import lcmc.vm.ui.resource.VMListInfo;
import org.apache.commons.collections15.keyvalue.MultiKey;
import org.apache.commons.collections15.map.LinkedMap;
import org.apache.commons.collections15.map.MultiKeyMap;

import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class holds cluster resource data in a tree. It shows panels that allow
 * to edit data of services etc.
 * Every resource has its Info object, that accessible through the tree view.
 */
@Named
public class ClusterBrowser extends Browser {
    private static final Logger LOG = LoggerFactory.getLogger(ClusterBrowser.class);
    public static final ImageIcon REMOVE_ICON = Tools
            .createImageIcon(Tools.getDefault("ClusterBrowser.RemoveIcon"));
    public static final ImageIcon REMOVE_ICON_SMALL = Tools
            .createImageIcon(Tools.getDefault("ClusterBrowser.RemoveIconSmall"));

    @Inject
    private Application application;
    @Inject
    private SwingUtils swingUtils;
    public static final Color SERVICE_STOPPED_FILL_PAINT = Tools.getDefaultColor("CRMGraph.FillPaintStopped");
    public static final String IDENT_4 = "    ";
    public static final String DRBD_RESOURCE_BOOL_TYPE_NAME = "boolean";
    public static final Collection<String> CRM_CLASSES = new ArrayList<String>();

    private static final String CRM_START_OPERATOR = "start";
    private static final String CRM_STOP_OPERATOR = "stop";
    private static final String CRM_STATUS_OPERATOR = "status";
    private static final String CRM_MONITOR_OPERATOR = "monitor";
    private static final String CRM_META_DATA_OPERATOR = "meta-data";
    private static final String CRM_VALIDATE_ALL_OPERATOR = "validate-all";
    public static final String CRM_PROMOTE_OPERATOR = "promote";
    public static final String CRM_DEMOTE_OPERATOR = "demote";

    private static final String CRM_DESC_PARAMETER = "description";
    private static final String CRM_INTERVAL_PARAMETER = "interval";
    private static final String CRM_TIMEOUT_PARAMETER = "timeout";
    private static final String CRM_START_DELAY_PARAMETER = "start-delay";
    private static final String CRM_DISABLED_PARAMETER = "disabled";
    private static final String CRM_ROLE_PARAMETER = "role";
    private static final String CRM_PREREQ_PARAMETER = "prereq";
    private static final String CRM_ON_FAIL_PARAMETER = "on-fail";

    public static final String[] CRM_OPERATIONS = { CRM_START_OPERATOR, CRM_PROMOTE_OPERATOR, CRM_DEMOTE_OPERATOR,
            CRM_STOP_OPERATOR, CRM_STATUS_OPERATOR, CRM_MONITOR_OPERATOR, CRM_META_DATA_OPERATOR,
            CRM_VALIDATE_ALL_OPERATOR };
    public static final Collection<String> CRM_OPERATIONS_WITH_IGNORED_DEFAULT = new ArrayList<String>();
    /** All parameters for the crm operations, so that it is possible to create
     * arguments for up_rsc_full_ops. */
    public static final String[] HB_OPERATION_PARAM_LIST = { CRM_DESC_PARAMETER, CRM_INTERVAL_PARAMETER,
            CRM_TIMEOUT_PARAMETER, CrmXml.PARAM_OCF_CHECK_LEVEL, CRM_START_DELAY_PARAMETER, CRM_DISABLED_PARAMETER,
            CRM_ROLE_PARAMETER, CRM_PREREQ_PARAMETER, CRM_ON_FAIL_PARAMETER };
    public static final String STARTING_PTEST_TOOLTIP = Tools.getString("ClusterBrowser.StartingPtest");
    private static final String CLUSTER_STATUS_ERROR = "---start---\r\nerror\r\n\r\n---done---\r\n";
    public static final ImageIcon CLUSTER_ICON_SMALL = Tools
            .createImageIcon(Tools.getDefault("ClusterBrowser.ClusterIconSmall"));
    /** String that appears as a tooltip in menu items if status was disabled.*/
    public static final String UNKNOWN_CLUSTER_STATUS_STRING = "unknown cluster status";
    private static final Collection<String> DEFAULT_OPERATION_PARAMS = new ArrayList<String>(
            Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER));
    private static final String RESET_STRING = "---reset---\r\n";
    private static final int RESET_STRING_LEN = RESET_STRING.length();
    /** Match ...by-res/r0 or by-res/r0/0 from DRBD 8.4. */
    private static final Pattern DEV_DRBD_BY_RES_PATTERN = Pattern
            .compile("^/dev/drbd/by-res/([^/]+)(?:/(\\d+))?$");
    /** Hash that holds all hb classes with descriptions that appear in the
     * pull down menus. */
    public static final Map<String, String> CRM_CLASS_MENU = new HashMap<String, String>();
    static {
        CRM_CLASS_MENU.put(ResourceAgent.OCF_CLASS_NAME, "OCF Resource Agents");
        CRM_CLASS_MENU.put(ResourceAgent.HEARTBEAT_CLASS_NAME, "Heartbeat 1 RAs (deprecated)");
        CRM_CLASS_MENU.put(ResourceAgent.LSB_CLASS_NAME, "LSB Init Scripts");
        CRM_CLASS_MENU.put(ResourceAgent.STONITH_CLASS_NAME, "Stonith Devices");
        CRM_CLASS_MENU.put(ResourceAgent.SERVICE_CLASS_NAME, "Upstart/Systemd Scripts");
        CRM_CLASS_MENU.put(ResourceAgent.SYSTEMD_CLASS_NAME, "Systemd Scripts");
        CRM_CLASS_MENU.put(ResourceAgent.UPSTART_CLASS_NAME, "Upstart Scripts");
    }
    static {
        CRM_CLASSES.add(ResourceAgent.OCF_CLASS_NAME);
        CRM_CLASSES.add(ResourceAgent.HEARTBEAT_CLASS_NAME);
        for (final String c : ResourceAgent.SERVICE_CLASSES) {
            CRM_CLASSES.add(c);
        }
        CRM_CLASSES.add(ResourceAgent.STONITH_CLASS_NAME);
    }
    static {
        CRM_OPERATIONS_WITH_IGNORED_DEFAULT.add(CRM_STATUS_OPERATOR);
        CRM_OPERATIONS_WITH_IGNORED_DEFAULT.add(CRM_META_DATA_OPERATOR);
        CRM_OPERATIONS_WITH_IGNORED_DEFAULT.add(CRM_VALIDATE_ALL_OPERATOR);
    }

    @Inject
    private Provider<DomainInfo> domainInfoProvider;
    @Inject
    @Named("hbConnectionInfo")
    private Provider<HbConnectionInfo> connectionInfoProvider;
    @Inject
    private Provider<AvailableServiceInfo> availableServiceInfoProvider;
    @Inject
    private Provider<DrbdXml> drbdXmlProvider;
    @Inject
    private Provider<ClusterStatus> clusterStatusProvider;
    @Resource(name = "categoryInfo")
    private CategoryInfo networksCategory;
    @Inject
    private Provider<ResourceAgentClassInfo> resourceAgentClassInfoProvider;
    @Inject
    private AvailableServicesInfo availableServicesInfo;
    @Inject
    private Provider<CRMInfo> crmInfoProvider;
    @Inject
    private Provider<VmsXml> vmsXmlProvider;
    @Inject
    private TreeMenuController treeMenuController;
    @Inject
    private ClusterEventBus clusterEventBus;
    @Inject
    private NetworkService networkService;
    @Inject
    private NetworkFactory networkFactory;
    @Inject
    private Provider<ResourceUpdater> resourceUpdaterProvider;

    public static String getClassMenuName(final String cl) {
        final String name = CRM_CLASS_MENU.get(cl);
        if (name == null) {
            return Tools.ucfirst(cl) + " scripts";
        }
        return name;
    }

    private Cluster cluster;
    private DefaultMutableTreeNode clusterHostsNode;
    private DefaultMutableTreeNode networksNode;
    private DefaultMutableTreeNode availableServicesNode;
    private DefaultMutableTreeNode crmNode;
    private DefaultMutableTreeNode servicesNode;
    private DefaultMutableTreeNode drbdNode;
    private DefaultMutableTreeNode vmsNode = null;

    /** name (hb type) + id to service info hash. */
    private final Map<String, Map<String, ServiceInfo>> nameToServiceInfoHash = new TreeMap<String, Map<String, ServiceInfo>>(
            String.CASE_INSENSITIVE_ORDER);
    private final Lock mNameToServiceLock = new ReentrantLock();
    private final Lock mDrbdResHashLock = new ReentrantLock();
    private final Map<String, ResourceInfo> drbdResourceNameHash = new HashMap<String, ResourceInfo>();
    private final Lock mDrbdDevHashLock = new ReentrantLock();
    /** DRBD resource device string to drbd resource info hash. */
    private final Map<String, VolumeInfo> drbdDeviceHash = new HashMap<String, VolumeInfo>();
    private final Lock mHeartbeatIdToService = new ReentrantLock();
    /** Heartbeat id to service info hash. */
    private final Map<String, ServiceInfo> heartbeatIdToServiceInfo = new HashMap<String, ServiceInfo>();
    @Inject
    private CrmGraph crmGraph;
    @Inject
    private DrbdGraph drbdGraph;
    private ClusterStatus clusterStatus;
    @Inject
    private CrmXml crmXml;
    @Inject
    private Access access;
    private DrbdXml drbdXml;
    private final ReadWriteLock mVmsLock = new ReentrantReadWriteLock();
    private final Lock mVmsReadLock = mVmsLock.readLock();
    private final Lock mVmsWriteLock = mVmsLock.writeLock();
    private final Lock mVmsUpdateLock = new ReentrantLock();
    private final Map<Host, VmsXml> vmsXML = new HashMap<Host, VmsXml>();
    private DRBDtestData drbdtestData;
    private boolean drbdStatusCanceledByUser = false;
    /** Whether hb status was canceled by user. */
    private boolean crmStatusCanceledByUser = false;
    private final Lock mPtestLock = new ReentrantLock();
    private final Lock mDrbdTestDataLock = new ReentrantLock();
    private volatile boolean serverStatusCanceled = false;
    private Host lastDcHostDetected = null;
    /** dc host as reported by crm. */
    private Host dcHostReportedByCrm = null;
    private ClusterViewPanel clusterViewPanel = null;
    /** Map to ResourceAgentClassInfo. */
    private final Map<String, ResourceAgentClassInfo> classInfoMap = new HashMap<String, ResourceAgentClassInfo>();
    /** Map from ResourceAgent to AvailableServicesInfo. */
    private final Map<ResourceAgent, AvailableServiceInfo> availableServiceMap = new HashMap<ResourceAgent, AvailableServiceInfo>();
    @Inject
    private ClusterHostsInfo clusterHostsInfo;
    @Inject
    private ServicesInfo servicesInfo;
    private RscDefaultsInfo rscDefaultsInfo = null;
    @Inject
    private Provider<RscDefaultsInfo> rscDefaultsInfoProvider;
    private final Lock mCrmStatusLock = new ReentrantLock();
    private final Map<String, List<String>> crmOperationParams = new LinkedHashMap<String, List<String>>();
    /** Not advanced operations. */
    private final MultiKeyMap<String, Integer> notAdvancedOperations = MultiKeyMap
            .decorate(new LinkedMap<MultiKey<String>, Integer>());
    private final Map<Host, String> hostDrbdParameters = new HashMap<Host, String>();
    private DefaultMutableTreeNode treeTop;
    @Inject
    private MainData mainData;
    @Inject
    private ProgressIndicator progressIndicator;
    @Inject
    private GlobalInfo globalInfo;

    @Inject
    private VMListInfo vmListInfo;

    public void init(final Cluster cluster) {
        this.cluster = cluster;
        clusterEventBus.register(this);
        crmGraph.initGraph(this);
        drbdGraph.initGraph(this);
        globalInfo.einit(Tools.getString("ClusterBrowser.Drbd"), this);
        treeTop = treeMenuController.createMenuTreeTop();
    }

    private void initOperations() {
        notAdvancedOperations.put(CRM_START_OPERATOR, CRM_TIMEOUT_PARAMETER, 1);
        notAdvancedOperations.put(CRM_STOP_OPERATOR, CRM_TIMEOUT_PARAMETER, 1);
        notAdvancedOperations.put(CRM_MONITOR_OPERATOR, CRM_TIMEOUT_PARAMETER, 1);
        notAdvancedOperations.put(CRM_MONITOR_OPERATOR, CRM_INTERVAL_PARAMETER, 1);
        notAdvancedOperations.put(CRM_MONITOR_OPERATOR, CrmXml.PARAM_OCF_CHECK_LEVEL, 1);

        crmOperationParams.put(CRM_START_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));
        crmOperationParams.put(CRM_STOP_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));
        crmOperationParams.put(CRM_META_DATA_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));
        crmOperationParams.put(CRM_VALIDATE_ALL_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));

        crmOperationParams.put(CRM_STATUS_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));

        // TODO: need two monitors for role='Slave' and 'Master' in
        // master/slave resources
        final Host dcHost = getDCHost();
        if (Tools.versionBeforePacemaker(dcHost)) {
            crmOperationParams.put(CRM_MONITOR_OPERATOR, new ArrayList<String>(
                    Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER, CrmXml.PARAM_OCF_CHECK_LEVEL)));
        } else {
            crmOperationParams.put(CRM_MONITOR_OPERATOR, new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER,
                    CRM_INTERVAL_PARAMETER, CrmXml.PARAM_OCF_CHECK_LEVEL, CRM_START_DELAY_PARAMETER)));
        }
        crmOperationParams.put(CRM_PROMOTE_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));
        crmOperationParams.put(CRM_DEMOTE_OPERATOR,
                new ArrayList<String>(Arrays.asList(CRM_TIMEOUT_PARAMETER, CRM_INTERVAL_PARAMETER)));
    }

    public Collection<String> getCrmOperationParams(final String operation) {
        final List<String> params = crmOperationParams.get(operation);
        if (params == null) {
            return DEFAULT_OPERATION_PARAMS;
        } else {
            return params;
        }
    }

    public boolean isCrmOperationAdvanced(final String operation, final String param) {
        if (!crmOperationParams.containsKey(operation)) {
            return !CRM_TIMEOUT_PARAMETER.equals(param);
        }
        return !notAdvancedOperations.containsKey(operation, param);
    }

    void setClusterViewPanel(final ClusterViewPanel clusterViewPanel) {
        this.clusterViewPanel = clusterViewPanel;
    }

    public ClusterViewPanel getClusterViewPanel() {
        return clusterViewPanel;
    }

    public Host[] getClusterHosts() {
        return cluster.getHostsArray();
    }

    public Cluster getCluster() {
        return cluster;
    }

    public void setRightComponentInView(final Info component) {
        clusterViewPanel.setRightComponentInView(this, component);
    }

    /**
     * Saves positions of service and block devices from the heartbeat and drbd
     * graphs to the config files on every node.
     */
    public void saveGraphPositions() {
        final Map<String, Point2D> positions = new HashMap<String, Point2D>();
        if (drbdGraph != null) {
            drbdGraph.getPositions(positions);
        }
        if (positions.isEmpty()) {
            return;
        }
        if (crmGraph != null) {
            crmGraph.getPositions(positions);
        }
        if (positions.isEmpty()) {
            return;
        }

        final Host[] hosts = getClusterHosts();
        for (final Host host : hosts) {
            host.saveGraphPositions(positions);
        }
    }

    public CrmGraph getCrmGraph() {
        return crmGraph;
    }

    public DrbdGraph getDrbdGraph() {
        return drbdGraph;
    }

    public boolean allHostsWithoutClusterStatus() {
        boolean hostsDown = true;
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            if (host.isCrmStatusOk()) {
                hostsDown = false;
                break;
            }
        }
        return hostsDown;
    }

    public boolean atLeastOneDrbddiskConfigured() {
        mHeartbeatIdToServiceLock();
        for (final Map.Entry<String, ServiceInfo> serviceInfoEntry : heartbeatIdToServiceInfo.entrySet()) {
            final ServiceInfo si = serviceInfoEntry.getValue();
            if (si.getResourceAgent().isDrbddisk()) {
                mHeartbeatIdToServiceUnlock();
                return true;
            }
        }
        mHeartbeatIdToServiceUnlock();
        return false;
    }

    public boolean isOneLinbitDrbdRaConfigured() {
        mHeartbeatIdToServiceLock();
        for (final Map.Entry<String, ServiceInfo> serviceInfoEntry : heartbeatIdToServiceInfo.entrySet()) {
            final ServiceInfo si = serviceInfoEntry.getValue();
            if (si.getResourceAgent().isLinbitDrbd()) {
                mHeartbeatIdToServiceUnlock();
                return true;
            }
        }
        mHeartbeatIdToServiceUnlock();
        return false;
    }

    void addVmsNode() {
        if (vmsNode == null) {
            vmListInfo.init(Tools.getString("ClusterBrowser.VMs"), this);
            vmsNode = treeMenuController.createMenuItem(treeTop, vmListInfo);
            treeMenuController.reloadNode(treeTop, true);
        }
    }

    /** Initializes cluster resources for cluster view. */
    void initClusterBrowser() {
        LOG.debug1("initClusterBrowser: start");
        /* hosts */
        clusterHostsInfo.init(Tools.getString("ClusterBrowser.ClusterHosts"), this);
        clusterHostsNode = treeMenuController.createMenuItem(treeTop, clusterHostsInfo);

        /* networks */
        networksCategory.init(Tools.getString("ClusterBrowser.Networks"), this);
        networksNode = treeMenuController.createMenuItem(treeTop, networksCategory);
        updateCommonNetworks(networkService.getCommonNetworks(cluster));

        /* drbd */
        drbdNode = treeMenuController.createMenuItem(treeTop, globalInfo);

        /* CRM */
        final CRMInfo crmInfo = crmInfoProvider.get();
        crmInfo.init(Tools.getString("ClusterBrowser.ClusterManager"), this);
        crmNode = treeMenuController.createMenuItem(treeTop, crmInfo);

        /* available services */
        availableServicesInfo.init(Tools.getString("ClusterBrowser.availableServices"), this);
        availableServicesNode = treeMenuController.createMenuItem(crmNode, availableServicesInfo);

        /* resource defaults */
        rscDefaultsInfo = rscDefaultsInfoProvider.get();
        rscDefaultsInfo.einit("rsc_defaults", this);
        /* services */
        servicesInfo.einit(Tools.getString("ClusterBrowser.Services"), this);
        servicesNode = treeMenuController.createMenuItem(crmNode, servicesInfo);
        addVmsNode();
        treeMenuController.selectPath(new Object[] { treeTop, crmNode });
        addDrbdProxyNodes();
        LOG.debug1("initClusterBrowser: end");
    }

    void addDrbdProxyNodes() {
        final Set<Host> clusterHosts = getCluster().getHosts();
        for (final Host pHost : getCluster().getProxyHosts()) {
            if (!clusterHosts.contains(pHost)) {
                globalInfo.addProxyHostNode(pHost);
            }
        }
    }

    void updateClusterResources(final Host[] clusterHosts) {
        LOG.debug1("start: update cluster resources");

        /* cluster hosts */
        treeMenuController.removeChildren(clusterHostsNode);
        for (final Host clusterHost : clusterHosts) {
            final HostBrowser hostBrowser = clusterHost.getBrowser();
            final DefaultMutableTreeNode resource = hostBrowser.getTreeTop();
            treeMenuController.addChild(clusterHostsNode, resource);
            crmGraph.addHost(hostBrowser.getHostInfo());
        }

        treeMenuController.reloadNode(clusterHostsNode, false);

        swingUtils.invokeLater(new Runnable() {
            @Override
            public void run() {
                crmGraph.scale();
            }
        });
        updateHeartbeatDrbdThread();
        LOG.debug1("end: update cluster resources");
    }

    /** Starts everything. */
    private void updateHeartbeatDrbdThread() {
        LOG.debug("updateHeartbeatDrbdThread: load cluster");
        final Thread tt = new Thread(new Runnable() {
            @Override
            public void run() {
                final Host[] hosts = cluster.getHostsArray();
                for (final Host host : hosts) {
                    host.getHostParser().waitForServerStatusLatch();
                    progressIndicator.stopProgressIndicator(host.getName(),
                            Tools.getString("ClusterBrowser.UpdatingServerInfo"));
                }
                swingUtils.invokeInEdt(new Runnable() {
                    @Override
                    public void run() {
                        getClusterViewPanel().setDisabledDuringLoad(false);
                        highlightServices();
                    }
                });
            }
        });
        tt.start();
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                final Host[] hosts = cluster.getHostsArray();
                swingUtils.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        for (final Host host : hosts) {
                            final HostBrowser hostBrowser = host.getBrowser();
                            drbdGraph.addHost(hostBrowser.getHostDrbdInfo());
                        }
                    }
                });
                int notConnectedCount = 0;
                Host firstHost = null;
                do { /* wait here until a host is connected. */
                    boolean notConnected = true;
                    for (final Host host : hosts) {
                        // TODO: fix that, use latches or callback
                        if (host.isConnected()) {
                            /* at least one connected. */
                            notConnected = false;
                            break;
                        }
                    }
                    if (notConnected) {
                        notConnectedCount++;
                    } else {
                        firstHost = getFirstHost();
                    }
                    if (firstHost == null) {
                        try {
                            Thread.sleep(30000);
                        } catch (final InterruptedException ex) {
                            Thread.currentThread().interrupt();
                        }
                        final boolean ok = cluster.connect(null, notConnectedCount < 1, notConnectedCount + 1);
                        if (!ok) {
                            break;
                        }
                    }
                } while (firstHost == null);
                if (firstHost == null) {
                    return;
                }
                if (!firstHost.isInCluster()) {
                    return;
                }

                LOG.debug1("updateHeartbeatDrbdThread: first host: " + firstHost);
                crmXml.init(firstHost, getServicesInfo());
                final ClusterStatus newClusterStatus = clusterStatusProvider.get();
                newClusterStatus.init(firstHost, crmXml);
                clusterStatus = newClusterStatus;
                initOperations();
                drbdXml = drbdXmlProvider.get();
                drbdXml.init(cluster.getHostsArray(), hostDrbdParameters);
                /* available services */
                final String clusterName = getCluster().getName();
                progressIndicator.startProgressIndicator(clusterName,
                        Tools.getString("ClusterBrowser.HbUpdateResources"));

                updateAvailableServices();
                progressIndicator.stopProgressIndicator(clusterName,
                        Tools.getString("ClusterBrowser.HbUpdateResources"));
                progressIndicator.startProgressIndicator(clusterName, Tools.getString("ClusterBrowser.DrbdUpdate"));
                progressIndicator.stopProgressIndicator(clusterName, Tools.getString("ClusterBrowser.DrbdUpdate"));
                cluster.getBrowser().startConnectionStatusOnAllHosts();
                cluster.getBrowser().startServerStatus();
                cluster.getBrowser().startDrbdStatusOnAllHosts();
                cluster.getBrowser().startCrmStatus();
                LOG.debug1("updateHeartbeatDrbdThread: cluster loading done");
            }
        };
        final Thread thread = new Thread(runnable);
        thread.start();
    }

    /**
     * Starts polling of the server status on all hosts, for all the stuff
     * that can change on the server on the fly, like for example the block
     * devices.
     */
    void startServerStatus() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    startServerStatus(host);
                }
            });
            thread.start();
        }
    }

    private void startPing(final Host host) {
        while (true) {
            host.startPing();
            Tools.sleep(10000);
            if (serverStatusCanceled) {
                break;
            }
        }
    }

    /** Start polling of the server status on one host. */
    void startServerStatus(final Host host) {
        final String hostName = host.getName();
        final CategoryInfo[] infosToUpdate = new CategoryInfo[] { clusterHostsInfo };
        while (true) {
            if (host.getHostParser().getWaitForServerStatusLatch()) {
                progressIndicator.startProgressIndicator(hostName,
                        Tools.getString("ClusterBrowser.UpdatingServerInfo"));
            }

            host.setIsLoading();
            host.getHostParser().startHWInfoDaemon(infosToUpdate, new ResourceGraph[] { drbdGraph, crmGraph });
            if (serverStatusCanceled) {
                break;
            }
            Tools.sleep(10000);
            if (serverStatusCanceled) {
                break;
            }
        }
    }

    public void updateServerStatus(final Host host) {
        swingUtils.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                drbdGraph.addHost(host.getBrowser().getHostDrbdInfo());
            }
        });
        swingUtils.invokeLater(new Runnable() {
            @Override
            public void run() {
                drbdGraph.scale();
            }
        });
        if (host.getHostParser().getWaitForServerStatusLatch()) {
            LOG.debug("updateServerStatus: " + host.getName() + " loading done");
        }
        host.getHostParser().serverStatusLatchDone();
        clusterHostsInfo.updateTable(CategoryInfo.MAIN_TABLE);
        for (final ResourceGraph graph : new ResourceGraph[] { drbdGraph, crmGraph }) {
            if (graph != null) {
                graph.repaint();
                graph.updatePopupMenus();
            }
        }
    }

    /** Updates VMs info. */
    public void periodicalVmsUpdate(final Host host) {
        final VmsXml newVmsXml = vmsXmlProvider.get();
        newVmsXml.init(host);
        if (newVmsXml.parseXml()) {
            vmsXmlPut(host, newVmsXml);
            updateVms();
        }
    }

    /** Updates VMs info. */
    public void periodicalVmsUpdate(final Host[] hosts) {
        periodicalVmsUpdate(Arrays.asList(hosts));
    }

    /** Updates VMs info. */
    public void periodicalVmsUpdate(final Iterable<Host> hosts) {
        boolean updated = false;
        for (final Host host : hosts) {
            final VmsXml newVmsXml = vmsXmlProvider.get();
            newVmsXml.init(host);
            if (newVmsXml.parseXml()) {
                vmsXmlPut(host, newVmsXml);
                updated = true;
            }
        }
        if (updated) {
            updateVms();
        }
    }

    /** Adds new vmsxml object to the hash. */
    public void vmsXmlPut(final Host host, final VmsXml newVmsXml) {
        mVmsWriteLock.lock();
        try {
            vmsXML.put(host, newVmsXml);
        } finally {
            mVmsWriteLock.unlock();
        }
    }

    public boolean isCancelServerStatus() {
        return serverStatusCanceled;
    }

    void startConnectionStatusOnAllHosts() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            final Thread pingThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    startPing(host);
                }
            });
            pingThread.start();
            host.startConnectionStatus();
        }
    }

    void startDrbdStatusOnAllHosts() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    startDrbdStatus(host);
                }
            });
            thread.start();
        }
    }

    public void stopDrbdStatusOnAllHosts() {
        drbdStatusCanceledByUser = true;
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            host.stopDrbdStatus();
        }
        for (final Host host : hosts) {
            host.waitForDrbdStatusFinish();
        }
    }

    void startDrbdStatus(final Host host) {
        final CountDownLatch firstTime = new CountDownLatch(1);
        host.setDrbdStatusOk(false);
        final String hostName = host.getName();
        /* now what we do if the status finished for the first time. */
        progressIndicator.startProgressIndicator(hostName, Tools.getString("ClusterBrowser.UpdatingDrbdStatus"));
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    firstTime.await();
                } catch (final InterruptedException ignored) {
                    Thread.currentThread().interrupt();
                }
                swingUtils.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        drbdGraph.scale();
                    }
                });
                progressIndicator.stopProgressIndicator(hostName,
                        Tools.getString("ClusterBrowser.UpdatingDrbdStatus"));
            }
        });
        thread.start();

        drbdStatusCanceledByUser = false;
        while (true) {
            host.execDrbdStatusCommand(new ExecCallback() {
                @Override
                public void done(final String ans) {
                    firstTime.countDown();
                    if (!host.isDrbdStatusOk()) {
                        host.setDrbdStatusOk(true);
                        drbdGraph.repaint();
                        LOG.debug1("startDrbdStatus: host: " + host.getName());
                        clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                    }
                }

                @Override
                public void doneError(final String answer, final int exitCode) {
                    firstTime.countDown();
                    LOG.debug1("startDrbdStatus: failed: " + host.getName() + " exit code: " + exitCode);
                    if (exitCode != 143 && exitCode != 100) {
                        // TODO: exit code is null -> 100 all of the
                        // sudden
                        /* was killed intentionally */
                        if (host.isDrbdStatusOk()) {
                            host.setDrbdStatusOk(false);
                            LOG.debug1("startDrbdStatus: host: " + host.getName());
                            drbdGraph.repaint();
                            clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                        }
                        if (exitCode == 255) {
                            /* looks like connection was lost */
                            //host.getSSH().forceReconnect();
                            //host.setConnected();
                        }
                    }
                    //TODO: repaint ok?
                    //repaintSplitPane();
                    //drbdGraph.updatePopupMenus();
                    //drbdGraph.repaint();
                }
            },

                    new NewOutputCallback() {
                        private final StringBuffer outputBuffer = new StringBuffer(300);

                        @Override
                        public void output(final String output) {
                            if ("--nm--".equals(output.trim())) {
                                if (host.isDrbdStatusOk()) {
                                    LOG.debug1("startDrbdStatus: host: " + host.getName());
                                    host.setDrbdStatusOk(false);
                                    drbdGraph.repaint();
                                    clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                                }
                                firstTime.countDown();
                                return;
                            }
                            firstTime.countDown();
                            if (!host.isDrbdStatusOk()) {
                                LOG.debug1("startDrbdStatus: host: " + host.getName());
                                host.setDrbdStatusOk(true);
                                drbdGraph.repaint();
                                clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                            }
                            outputBuffer.append(output);
                            String drbdConfig, event;
                            boolean drbdUpdate = false;
                            boolean eventUpdate = false;
                            do {
                                host.drbdStatusLock();
                                drbdConfig = host.getHostParser().getOutput("drbd", outputBuffer);
                                if (drbdConfig != null) {
                                    final DrbdXml newDrbdXml = drbdXmlProvider.get();
                                    newDrbdXml.init(cluster.getHostsArray(), hostDrbdParameters);
                                    newDrbdXml.update(drbdConfig);
                                    drbdXml = newDrbdXml;
                                    drbdUpdate = true;
                                    firstTime.countDown();
                                }
                                host.drbdStatusUnlock();
                                event = host.getHostParser().getOutput("event", outputBuffer);
                                if (event != null) {
                                    if (drbdXml.parseDrbdEvent(host.getName(), drbdGraph, event)) {
                                        host.setDrbdStatusOk(true);
                                        eventUpdate = true;
                                    }
                                }
                            } while (event != null || drbdConfig != null);
                            Tools.chomp(outputBuffer);
                            if (drbdUpdate) {
                                swingUtils.invokeLater(new Runnable() {
                                    @Override
                                    public void run() {
                                        globalInfo.setParameters();
                                        updateDrbdResources();
                                    }
                                });
                            }
                            if (eventUpdate) {
                                drbdGraph.repaint();
                                LOG.debug1("drbd status update: " + host.getName());
                                clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                                firstTime.countDown();
                                final Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        repaintSplitPane();
                                        drbdGraph.updatePopupMenus();
                                        swingUtils.invokeInEdt(new Runnable() {
                                            @Override
                                            public void run() {
                                                treeMenuController.repaintMenuTree();
                                            }
                                        });
                                    }
                                });
                                thread.start();
                            }
                        }
                    });
            host.waitForHostAndDrbd();
            host.waitForDrbdStatusFinish();
            if (drbdStatusCanceledByUser) {
                break;
            }
        }
    }

    public void stopCrmStatus() {
        crmStatusCanceledByUser = true;
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            host.stopCrmStatus();
        }
    }

    public void stopServerStatus() {
        serverStatusCanceled = true;
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            host.getHostParser().stopServerStatus();
        }
    }

    public boolean crmStatusFailed() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            if (host.isCrmStatusOk()) {
                return false;
            }
        }
        return true;
    }

    /** Sets crm status (failed / not failed for every node). */
    void setCrmStatus() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            final String online = clusterStatus.isOnlineNode(host.getName());
            if ("yes".equals(online)) {
                setCrmStatus(host, true);
            } else {
                setCrmStatus(host, false);
            }
        }
    }

    void startClStatusProgressIndicator(final String clusterName) {
        progressIndicator.startProgressIndicator(clusterName, Tools.getString("ClusterBrowser.HbUpdateStatus"));
    }

    void stopClStatusProgressIndicator(final String clusterName) {
        progressIndicator.stopProgressIndicator(clusterName, Tools.getString("ClusterBrowser.HbUpdateStatus"));
    }

    /** Sets status and checks if it changes and if it does some action will be
     * performed. */
    private void setCrmStatus(final Host host, final boolean status) {
        final boolean oldStatus = host.isCrmStatusOk();
        host.setCrmStatusOk(status);
        if (oldStatus != status) {
            treeMenuController.nodeChanged(servicesNode);
        }
    }

    public void parseClusterOutput(final String output, final StringBuffer clusterStatusOutput, final Host host,
            final CountDownLatch firstTime, final Application.RunMode runMode) {
        final ClusterStatus clusterStatus0 = this.clusterStatus;
        clStatusLock();
        if (crmStatusCanceledByUser || clusterStatus0 == null) {
            clStatusUnlock();
            firstTime.countDown();
            return;
        }
        if (output == null || "".equals(output)) {
            clusterStatus0.setOnlineNode(host.getName(), "no");
            setCrmStatus(host, false);
            firstTime.countDown();
        } else {
            // TODO: if we get ERROR:... show it somewhere
            clusterStatusOutput.append(output);
            /* removes the string from the output. */
            int s = clusterStatusOutput.indexOf(RESET_STRING);
            while (s >= 0) {
                clusterStatusOutput.delete(s, s + RESET_STRING_LEN);
                s = clusterStatusOutput.indexOf(RESET_STRING);
            }
            if (clusterStatusOutput.length() > 12) {
                final String e = clusterStatusOutput.substring(clusterStatusOutput.length() - 12);
                if (e.trim().equals("---done---")) {
                    final int i = clusterStatusOutput.lastIndexOf("---start---");
                    if (i >= 0) {
                        if (clusterStatusOutput.indexOf("is stopped") >= 0) {
                            /* TODO: heartbeat's not running. */
                        } else {
                            final String status = clusterStatusOutput.substring(i);
                            clusterStatusOutput.delete(0, clusterStatusOutput.length());
                            if (CLUSTER_STATUS_ERROR.equals(status)) {
                                final boolean oldStatus = host.isCrmStatusOk();
                                clusterStatus0.setOnlineNode(host.getName(), "no");
                                setCrmStatus(host, false);
                                if (oldStatus) {
                                    crmGraph.repaint();
                                }
                            } else {
                                if (clusterStatus0.parseStatus(status)) {
                                    LOG.debug1("processClusterOutput: host: " + host.getName());
                                    final ServicesInfo ssi = servicesInfo;
                                    rscDefaultsInfo.setParameters(clusterStatus0.getRscDefaultsValuePairs());
                                    ssi.setGlobalConfig(clusterStatus0);
                                    resourceUpdaterProvider.get().updateAllResources(ssi, ssi.getBrowser(),
                                            clusterStatus0, runMode);
                                    if (firstTime.getCount() == 1) {
                                        /* one more time so that id-refs work.*/
                                        resourceUpdaterProvider.get().updateAllResources(ssi, ssi.getBrowser(),
                                                clusterStatus0, runMode);
                                    }
                                    treeMenuController.repaintMenuTree();
                                    clusterHostsInfo.updateTable(ClusterHostsInfo.MAIN_TABLE);
                                }
                                final String online = clusterStatus0.isOnlineNode(host.getName());
                                if ("yes".equals(online)) {
                                    setCrmStatus(host, true);
                                    setCrmStatus();
                                } else {
                                    setCrmStatus(host, false);
                                }
                            }
                        }
                        firstTime.countDown();
                    }
                }
            }
            Tools.chomp(clusterStatusOutput);
        }
        clStatusUnlock();
    }

    void startCrmStatus() {
        final CountDownLatch firstTime = new CountDownLatch(1);
        final String clusterName = getCluster().getName();
        startClStatusProgressIndicator(clusterName);
        final Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    firstTime.await();
                } catch (final InterruptedException ignored) {
                    Thread.currentThread().interrupt();
                }
                if (crmStatusFailed()) {
                    progressIndicator.progressIndicatorFailed(clusterName,
                            Tools.getString("ClusterBrowser.ClusterStatusFailed"));
                } else {
                    swingUtils.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            crmGraph.scale();
                        }
                    });
                }
                stopClStatusProgressIndicator(clusterName);
            }
        });
        thread.start();
        crmStatusCanceledByUser = false;
        final Application.RunMode runMode = Application.RunMode.LIVE;
        while (true) {
            final Host host = getDCHost();
            if (host == null) {
                try {
                    Thread.sleep(5000);
                } catch (final InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
                continue;
            }
            //clStatusCanceled = false;
            host.execCrmStatusCommand(new ExecCallback() {
                @Override
                public void done(final String answer) {
                    final String online = clusterStatus.isOnlineNode(host.getName());
                    setCrmStatus(host, "yes".equals(online));
                    firstTime.countDown();
                }

                @Override
                public void doneError(final String answer, final int exitCode) {
                    if (firstTime.getCount() == 1) {
                        LOG.debug2("startClStatus: status failed: " + host.getName() + ", ec: " + exitCode);
                    }
                    clStatusLock();
                    clusterStatus.setOnlineNode(host.getName(), "no");
                    setCrmStatus(host, false);
                    clusterStatus.setDC(null);
                    clStatusUnlock();
                    if (exitCode == 255) {
                        /* looks like connection was lost */
                        //crmGraph.repaint();
                        //host.getSSH().forceReconnect();
                        //host.setConnected();
                    }
                    firstTime.countDown();
                }
            },

                    new NewOutputCallback() {
                        //TODO: check this buffer's size
                        private final StringBuffer clusterStatusOutput = new StringBuffer(300);

                        @Override
                        public void output(final String output) {
                            parseClusterOutput(output, clusterStatusOutput, host, firstTime, runMode);
                        }
                    });
            host.waitForCrmStatusFinish();
            if (crmStatusCanceledByUser) {
                break;
            }
            try {
                Thread.sleep(5000);
            } catch (final InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /** Returns 'add service' list for menus. */
    public List<ResourceAgent> globalGetAddServiceList(final String cl) {
        return crmXml.getServices(cl);
    }

    /** Updates available services. */
    private void updateAvailableServices() {
        LOG.debug("updateAvailableServices: start");
        treeMenuController.removeChildren(availableServicesNode);
        for (final String crmClass : CRM_CLASSES) {
            final ResourceAgentClassInfo raci = resourceAgentClassInfoProvider.get();
            raci.init(crmClass, this);
            classInfoMap.put(crmClass, raci);
            final DefaultMutableTreeNode classNode = treeMenuController.createMenuItem(availableServicesNode, raci);
            for (final ResourceAgent resourceAgent : crmXml.getServices(crmClass)) {
                final AvailableServiceInfo availableService = availableServiceInfoProvider.get();
                availableService.init(resourceAgent, this);
                availableServiceMap.put(resourceAgent, availableService);
                treeMenuController.createMenuItem(classNode, availableService);
            }
        }
    }

    /** Updates VM nodes. */
    public void updateVms() {
        LOG.debug1("updateVMS: status update");
        final Collection<String> domainNames = new TreeSet<String>();
        for (final Host host : getClusterHosts()) {
            final VmsXml vmsXml = getVmsXml(host);
            if (vmsXml != null) {
                domainNames.addAll(vmsXml.getDomainNames());
            }
        }
        final Collection<DefaultMutableTreeNode> nodesToRemove = new ArrayList<DefaultMutableTreeNode>();
        final Collection<DomainInfo> currentVMSVDIs = new ArrayList<DomainInfo>();

        mVmsUpdateLock.lock();
        boolean nodeChanged = false;
        if (vmsNode != null) {
            for (final Object info : treeMenuController.nodesToInfos(vmsNode.children())) {
                final DomainInfo domainInfo = (DomainInfo) info;
                if (domainNames.contains(domainInfo.toString())) {
                    /* keeping */
                    currentVMSVDIs.add(domainInfo);
                    domainNames.remove(domainInfo.toString());
                    domainInfo.updateParameters(); /* update old */
                } else {
                    if (!domainInfo.getResource().isNew()) {
                        /* remove not existing vms */
                        nodesToRemove.add(domainInfo.getNode());
                        domainInfo.setNode(null);
                        nodeChanged = true;
                    }
                }
            }
        }

        treeMenuController.removeFromParent(nodesToRemove);
        if (vmsNode == null) {
            mVmsUpdateLock.unlock();
            return;
        }
        for (final String domainName : domainNames) {
            int i = 0;
            for (final Object info : treeMenuController.nodesToInfos(vmsNode.children())) {
                final DomainInfo domainInfo = (DomainInfo) info;
                final String name = domainInfo.getName();
                if (domainName != null && name != null && domainName.compareTo(domainInfo.getName()) < 0) {
                    break;
                }
                i++;
            }
            /* add new vms nodes */
            final DomainInfo domainInfo = domainInfoProvider.get();
            domainInfo.einit(domainName, this);
            currentVMSVDIs.add(domainInfo);
            treeMenuController.createMenuItem(vmsNode, domainInfo, i);
            domainInfo.updateParameters();
            nodeChanged = true;
        }
        mVmsUpdateLock.unlock();
        if (nodeChanged) {
            treeMenuController.reloadNode(vmsNode, false);
        }
        for (final ServiceInfo si : getExistingServiceList(null)) {
            final DomainInfo vmsvdi = si.connectWithVMS();
            if (vmsvdi != null) {
                /* keep the not connected ones.*/
                currentVMSVDIs.remove(vmsvdi);
            }
        }
        for (final DomainInfo vmsvdi : currentVMSVDIs) {
            vmsvdi.setUsedByCRM(false);
        }
        final VMListInfo vmsi = (VMListInfo) vmsNode.getUserObject();
        if (vmsi != null) {
            vmsi.updateTable(VMListInfo.MAIN_TABLE);
        }
    }

    public VMListInfo getVmsInfo() {
        return (VMListInfo) vmsNode.getUserObject();
    }

    public void updateDrbdResources() {
        swingUtils.isSwingThread();
        drbdStatusLock();
        final DrbdXml dxml = drbdXml;
        if (dxml == null) {
            drbdStatusUnlock();
            return;
        }
        final Application.RunMode runMode = Application.RunMode.LIVE;
        boolean atLeastOneAdded = false;
        for (final Table.Cell<String, String, String> cell : dxml.getResourceDeviceMap().cellSet()) {
            final String resName = cell.getRowKey();
            final String volumeNr = cell.getColumnKey();
            final String drbdDev = cell.getValue();

            final Map<String, String> hostDiskMap = dxml.getHostDiskMap(resName, volumeNr);
            if (hostDiskMap == null) {
                continue;
            }
            BlockDevInfo bd1 = null;
            BlockDevInfo bd2 = null;
            for (final String hostName : hostDiskMap.keySet()) {
                if (!cluster.contains(hostName)) {
                    continue;
                }
                final String disk = hostDiskMap.get(hostName);
                final BlockDevInfo bdi = drbdGraph.findBlockDevInfo(hostName, disk);
                if (bdi == null) {
                    if (getDrbdDeviceHash().containsKey(disk)) {
                        /* TODO: ignoring stacked device */
                        putDrbdDevHash();
                        continue;
                    } else {
                        putDrbdDevHash();
                        LOG.appWarning(
                                "updateDrbdResources: could not find disk: " + disk + " on host: " + hostName);
                        continue;
                    }
                }
                bdi.setParameters(resName);
                if (bd1 == null) {
                    bd1 = bdi;
                } else {
                    bd2 = bdi;
                }
            }
            if (bd1 != null && bd2 != null) {
                /* add DRBD resource */
                ResourceInfo dri = getDrbdResourceNameHash().get(resName);
                putDrbdResHash();
                final List<BlockDevInfo> bdis = new ArrayList<BlockDevInfo>(Arrays.asList(bd1, bd2));
                if (dri == null) {
                    dri = globalInfo.addDrbdResource(resName, VolumeInfo.getHostsFromBlockDevices(bdis), runMode);
                    atLeastOneAdded = true;
                }
                VolumeInfo dvi = dri.getDrbdVolumeInfo(volumeNr);
                if (dvi == null) {
                    dvi = globalInfo.addDrbdVolume(dri, volumeNr, drbdDev, bdis, runMode);
                    atLeastOneAdded = true;
                }
                dri.setParameters();
                dvi.setParameters();
                final ResourceInfo dri0 = dri;
                dri0.getInfoPanel();
            }
        }
        //TODO: it would remove it during drbd wizards
        //killRemovedVolumes(dxml.getResourceDeviceMap());
        drbdStatusUnlock();
        if (atLeastOneAdded) {
            globalInfo.getInfoPanel();
            globalInfo.setAllApplyButtons();
            globalInfo.reloadDRBDResourceComboBoxes();
            drbdGraph.scale();
        }
    }

    @Subscribe
    public void onUpdateCommonNetworks(final NetworkChangedEvent event) {
        if (cluster != event.getCluster()) {
            return;
        }
        updateCommonNetworks(event.getCommonNetworks());
    }

    private void updateCommonNetworks(final Collection<Network> networks) {
        treeMenuController.removeChildren(networksNode);
        for (final Network network : networks) {
            final NetworkPresenter networkPresenter = networkFactory.createPresenter(cluster, network);
            treeMenuController.createMenuItem(networksNode, networkPresenter);
        }
        treeMenuController.reloadNode(networksNode, false);
    }

    /**
     * Returns first host. Used for heartbeat commands, that can be
     * executed on any host.
     * It changes terminal panel to this host.
     */
    Host getFirstHost() {
        /* TODO: if none of the hosts is connected the null causes error during
         * loading. */
        final Host[] hosts = getClusterHosts();
        for (final Host host : hosts) {
            if (host.isConnected()) {
                return host;
            }
        }
        //if (hosts != null && hosts.length > 0) {
        //    return hosts[0];
        //}
        //LOG.appError("Could not find any hosts");
        return null;
    }

    /** Returns whether the host is in stand by. */
    public boolean isStandby(final Host host, final Application.RunMode runMode) {
        final ClusterStatus cl = clusterStatus;
        if (cl == null) {
            return false;
        }
        final String standby = cl.getNodeParameter(host.getName().toLowerCase(Locale.US), "standby", runMode);
        return "on".equals(standby) || "true".equals(standby);
    }

    /** Returns whether the host is the real dc host as reported by dc. */
    public boolean isRealDcHost(final Host host) {
        return host.equals(dcHostReportedByCrm);
    }

    /**
     * Finds and returns DC host.
     * TODO: document what's going on.
     */
    public Host getDCHost() {
        String dc = null;
        final ClusterStatus cl = clusterStatus;
        if (cl != null) {
            dc = cl.getDC();
        }
        final List<Host> hosts = new ArrayList<Host>();
        int lastHostIndex = 0;
        int i = 0;
        Host dcHost = null;
        for (final Host host : getClusterHosts()) {
            if (host == lastDcHostDetected) {
                lastHostIndex = i;
            }
            if (host.getName().equals(dc) && host.isCrmStatusOk() && !host.getHostParser().isCommLayerStarting()
                    && !host.getHostParser().isCommLayerStopping()
                    && (host.getHostParser().isHeartbeatRunning() || host.getHostParser().isCorosyncRunning()
                            || host.getHostParser().isOpenaisRunning())) {
                dcHost = host;
                break;
            }
            hosts.add(host);

            i++;
        }
        if (dcHost == null) {
            int ix = lastHostIndex;
            do {
                ix++;
                if (ix > hosts.size() - 1) {
                    ix = 0;
                }
                if (hosts.get(ix).isConnected() && (hosts.get(ix).getHostParser().isHeartbeatRunning()
                        || hosts.get(ix).getHostParser().isCorosyncRunning()
                        || hosts.get(ix).getHostParser().isOpenaisRunning())) {
                    lastDcHostDetected = hosts.get(ix);
                    break;
                }
            } while (ix != lastHostIndex);
            dcHost = lastDcHostDetected;
            dcHostReportedByCrm = null;
            if (dcHost == null) {
                dcHost = hosts.get(0);
            }
        } else {
            dcHostReportedByCrm = dcHost;
        }

        lastDcHostDetected = dcHost;
        return dcHost;
    }

    public void drbdStatusLock() {
        for (final Host host : getClusterHosts()) {
            host.drbdStatusLock();
        }
    }

    public void drbdStatusUnlock() {
        final Host[] hosts = getClusterHosts();
        for (int i = hosts.length - 1; i >= 0; i--) {
            hosts[i].drbdStatusUnlock();
        }
    }

    public void vmStatusLock() {
        for (final Host host : getClusterHosts()) {
            host.vmStatusLock();
        }
    }

    public void vmStatusUnlock() {
        final Host[] hosts = getClusterHosts();
        for (int i = hosts.length - 1; i >= 0; i--) {
            hosts[i].vmStatusUnlock();
        }
    }

    void highlightDrbd() {
        treeMenuController.reloadNode(drbdNode, true);
    }

    public void highlightServices() {
        if (getClusterViewPanel().isDisabledDuringLoad()) {
            return;
        }
        treeMenuController.selectPath(new Object[] { treeTop, crmNode, servicesNode });
    }

    public ServiceInfo getServiceInfoFromCRMId(final String crmId) {
        mHeartbeatIdToServiceLock();
        final ServiceInfo serviceInfo = heartbeatIdToServiceInfo.get(crmId);
        mHeartbeatIdToServiceUnlock();
        return serviceInfo;
    }

    public boolean isCrmId(final String crmId) {
        mHeartbeatIdToServiceLock();
        final boolean ret = heartbeatIdToServiceInfo.containsKey(crmId);
        mHeartbeatIdToServiceUnlock();
        return ret;
    }

    public void mHeartbeatIdToServiceLock() {
        mHeartbeatIdToService.lock();
    }

    public void mHeartbeatIdToServiceUnlock() {
        mHeartbeatIdToService.unlock();
    }

    /** Returns heartbeatIdToServiceInfo hash. You have to lock it. */
    public Map<String, ServiceInfo> getHeartbeatIdToServiceInfo() {
        return heartbeatIdToServiceInfo;
    }

    /** Returns ServiceInfo object identified by name and id. */
    public ServiceInfo getServiceInfoFromId(final String name, final String id) {
        lockNameToServiceInfo();
        final Map<String, ServiceInfo> idToInfoHash = nameToServiceInfoHash.get(name);
        if (idToInfoHash == null) {
            unlockNameToServiceInfo();
            return null;
        }
        final ServiceInfo si = idToInfoHash.get(id);
        unlockNameToServiceInfo();
        return si;
    }

    /** Returns the name, id to service info hash. */
    public Map<String, Map<String, ServiceInfo>> getNameToServiceInfoHash() {
        return nameToServiceInfoHash;
    }

    public void lockNameToServiceInfo() {
        mNameToServiceLock.lock();
    }

    public void unlockNameToServiceInfo() {
        mNameToServiceLock.unlock();
    }

    /** Returns 'existing service' list for graph popup menu. */
    public List<ServiceInfo> getExistingServiceList(final ServiceInfo p) {
        final List<ServiceInfo> existingServiceList = new ArrayList<ServiceInfo>();
        lockNameToServiceInfo();
        for (final String name : nameToServiceInfoHash.keySet()) {
            final Map<String, ServiceInfo> idHash = nameToServiceInfoHash.get(name);
            for (final String id : idHash.keySet()) {
                final ServiceInfo si = idHash.get(id);
                if (si.getService().isOrphaned()) {
                    continue;
                }
                final GroupInfo gi = si.getGroupInfo();
                ServiceInfo sigi = si;
                if (gi != null) {
                    sigi = gi;
                    // TODO: it does not work here
                }
                if (p == null || !getCrmGraph().existsInThePath(sigi, p)) {
                    existingServiceList.add(si);
                }
            }
        }
        unlockNameToServiceInfo();
        return existingServiceList;
    }

    public void removeFromServiceInfoHash(final ServiceInfo serviceInfo) {
        // TODO: it comes here twice sometimes
        final Service service = serviceInfo.getService();
        lockNameToServiceInfo();
        final Map<String, ServiceInfo> idToInfoHash = nameToServiceInfoHash.get(service.getName());
        if (idToInfoHash != null) {
            idToInfoHash.remove(service.getId());
            if (idToInfoHash.isEmpty()) {
                nameToServiceInfoHash.remove(service.getName());
            }
        }
        unlockNameToServiceInfo();
    }

    /** Returns nameToServiceInfoHash for the specified service.
     *  You must lock it when you use it. */
    public Map<String, ServiceInfo> getNameToServiceInfoHash(final String name) {
        return nameToServiceInfoHash.get(name);
    }

    /**
     * Adds heartbeat id from service to the list. If service does not have an
     * id it is generated.
     */
    public void addToHeartbeatIdList(final ServiceInfo si) {
        final String id = si.getService().getId();
        String pmId = si.getService().getCrmId();
        if (pmId == null) {
            if (Application.PACEMAKER_GROUP_NAME.equals(si.getService().getName())) {
                pmId = Service.GRP_ID_PREFIX;
            } else if (Application.PM_CLONE_SET_NAME.equals(si.getService().getName())
                    || Application.PM_MASTER_SLAVE_SET_NAME.equals(si.getService().getName())) {
                if (si.getService().isMaster()) {
                    pmId = Service.MS_ID_PREFIX;
                } else {
                    pmId = Service.CL_ID_PREFIX;
                }
            } else if (si.getResourceAgent().isStonith()) {
                pmId = Service.STONITH_ID_PREFIX + si.getService().getName() + '_';
            } else {
                pmId = Service.RES_ID_PREFIX + si.getService().getName() + '_';
            }
            final String newPmId;
            if (id == null) {
                /* first time, no pm id is set */
                newPmId = pmId + '1';
                si.getService().setId("1");
            } else {
                newPmId = pmId + id;
                si.getService().setCrmId(newPmId);
            }
            mHeartbeatIdToServiceLock();
            heartbeatIdToServiceInfo.put(newPmId, si);
            mHeartbeatIdToServiceUnlock();
        } else {
            mHeartbeatIdToServiceLock();
            if (heartbeatIdToServiceInfo.get(pmId) == null) {
                heartbeatIdToServiceInfo.put(pmId, si);
            }
            mHeartbeatIdToServiceUnlock();
        }
    }

    /**
     * Deletes caches of all Filesystem infoPanels.
     * This is useful if something have changed.
     */
    @Deprecated
    public void resetFilesystems() {
        mHeartbeatIdToServiceLock();
        for (final String hbId : heartbeatIdToServiceInfo.keySet()) {
            final ServiceInfo si = heartbeatIdToServiceInfo.get(hbId);
            if (si.getName().equals("Filesystem")) {
                si.setInfoPanel(null);
            }
        }
        mHeartbeatIdToServiceUnlock();
    }

    /** Check if the id exists for the service already, if so add _$index
     * to it. */
    public String getFreeId(final String serviceName, final String id) {
        if (id == null) {
            return null;
        }
        String newId = id;
        lockNameToServiceInfo();
        try {
            final Map<String, ServiceInfo> idToInfoHash = nameToServiceInfoHash.get(serviceName);
            if (idToInfoHash != null) {
                int index = 2;
                while (idToInfoHash.containsKey(newId)) {
                    newId = id + '_' + index;
                    index++;
                }
            }
        } finally {
            unlockNameToServiceInfo();
        }
        return newId;
    }

    /**
     * Adds ServiceInfo in the name to ServiceInfo hash. Id and name
     * are taken from serviceInfo object. nameToServiceInfoHash
     * contains a hash with id as a key and ServiceInfo as a value.
     */
    public void addNameToServiceInfoHash(final ServiceInfo serviceInfo) {
        /* add to the hash with service name and id as keys */
        final Service service = serviceInfo.getService();
        lockNameToServiceInfo();
        Map<String, ServiceInfo> idToInfoHash = nameToServiceInfoHash.get(service.getName());
        String csPmId = null;
        final ServiceInfo cs = serviceInfo.getContainedService();
        if (cs != null) {
            csPmId = cs.getService().getName() + '_' + cs.getService().getId();
        }
        if (idToInfoHash == null) {
            idToInfoHash = new TreeMap<String, ServiceInfo>(String.CASE_INSENSITIVE_ORDER);
            if (service.getId() == null) {
                if (csPmId == null) {
                    service.setId("1");
                } else {
                    service.setIdAndCrmId(csPmId);
                }
            }
        } else {
            if (service.getId() == null) {
                int index = 0;
                for (final String id : idToInfoHash.keySet()) {
                    final Pattern p;
                    if (csPmId == null) {
                        p = Pattern.compile("^(\\d+)$");
                    } else {
                        /* ms */
                        p = Pattern.compile('^' + csPmId + "_(\\d+)$");
                        if (csPmId.equals(id)) {
                            index++;
                        }
                    }

                    final Matcher m = p.matcher(id);
                    if (m.matches()) {
                        try {
                            final int i = Integer.parseInt(m.group(1));
                            if (i > index) {
                                index = i;
                            }
                        } catch (final NumberFormatException nfe) {
                            LOG.appWarning("addNameToServiceInfoHash: could not parse: " + m.group(1));
                        }
                    }
                }

                if (csPmId == null) {
                    service.setId(Integer.toString(index + 1));
                } else {
                    /* ms */
                    if (index == 0) {
                        service.setIdAndCrmId(csPmId);
                    } else {
                        service.setIdAndCrmId(csPmId + '_' + Integer.toString(index + 1));
                    }
                }
            }
        }
        idToInfoHash.put(service.getId(), serviceInfo);
        nameToServiceInfoHash.put(service.getName(), idToInfoHash);
        unlockNameToServiceInfo();
    }

    /**
     * Returns true if user wants the heartbeat:drbd, which is not recommended.
     */
    public boolean hbDrbdConfirmDialog() {
        return application.confirmDialog(Tools.getString("ClusterBrowser.confirmHbDrbd.Title"),
                Tools.getString("ClusterBrowser.confirmHbDrbd.Description"),
                Tools.getString("ClusterBrowser.confirmHbDrbd.Yes"),
                Tools.getString("ClusterBrowser.confirmHbDrbd.No"));
    }

    public boolean isDrbddiskRAPreferred() {
        return Tools.versionBeforePacemaker(getDCHost());
    }

    /**
     * Returns true if user wants the linbit:drbd even, for old version of
     * hb or simply true if we have pacemaker.
     */
    public boolean linbitDrbdConfirmDialog() {
        if (isDrbddiskRAPreferred()) {
            final String desc = Tools.getString("ClusterBrowser.confirmLinbitDrbd.Description");

            final Host dcHost = getDCHost();
            final String hbV = dcHost.getHostParser().getHeartbeatVersion();
            return application.confirmDialog(Tools.getString("ClusterBrowser.confirmLinbitDrbd.Title"),
                    desc.replaceAll("@VERSION@", hbV), Tools.getString("ClusterBrowser.confirmLinbitDrbd.Yes"),
                    Tools.getString("ClusterBrowser.confirmLinbitDrbd.No"));
        }
        return true;
    }

    void startHeartbeatsOnAllNodes() {
        final Host[] hosts = cluster.getHostsArray();
        for (final Host host : hosts) {
            Heartbeat.startHeartbeat(host);
        }
    }

    /**
     * This is called from crm graph.
     */
    public HbConnectionInfo getNewHbConnectionInfo() {
        final HbConnectionInfo connectionInfo = connectionInfoProvider.get();
        connectionInfo.init(this);
        //connectionInfo.getInfoPanel();
        return connectionInfo;
    }

    public ClusterStatus getClusterStatus() {
        return clusterStatus;
    }

    public DRBDtestData getDRBDtestData() {
        drbdTestDataLockAcquire();
        final DRBDtestData dtd = drbdtestData;
        drbdTestDataLockRelease();
        return dtd;
    }

    public void setDRBDtestData(final DRBDtestData drbdtestData) {
        drbdTestDataLockAcquire();
        this.drbdtestData = drbdtestData;
        drbdTestDataLockRelease();
    }

    public void ptestLockAcquire() {
        mPtestLock.lock();
    }

    public void ptestLockRelease() {
        mPtestLock.unlock();
    }

    protected void drbdTestDataLockAcquire() {
        mDrbdTestDataLock.lock();
    }

    protected void drbdTestDataLockRelease() {
        mDrbdTestDataLock.unlock();
    }

    /** Returns xml from cluster manager. */
    public CrmXml getCrmXml() {
        return crmXml;
    }

    public DrbdXml getDrbdXml() {
        return drbdXml;
    }

    public void setDrbdXml(final DrbdXml drbdXml) {
        this.drbdXml = drbdXml;
    }

    public DefaultMutableTreeNode getDrbdNode() {
        return drbdNode;
    }

    public TreeNode getClusterHostsNode() {
        return clusterHostsNode;
    }

    public DefaultMutableTreeNode getServicesNode() {
        return servicesNode;
    }

    public TreeNode getNetworksNode() {
        return networksNode;
    }

    /**
     * Returns a hash from drbd device to drbd volume info. putDrbdDevHash
     * must follow after you're done. */
    public Map<String, VolumeInfo> getDrbdDeviceHash() {
        mDrbdDevHashLock.lock();
        return drbdDeviceHash;
    }

    /** Unlock drbd dev hash. */
    public void putDrbdDevHash() {
        mDrbdDevHashLock.unlock();
    }

    /**
     * Return volume info object from the drbd block device name.
     * /dev/drbd/by-res/r0
     * /dev/drbd/by-res/r0/0
     * /dev/drbd0
     */
    public VolumeInfo getDrbdVolumeFromDev(final CharSequence dev) {
        if (dev == null) {
            return null;
        }
        final Matcher m = DEV_DRBD_BY_RES_PATTERN.matcher(dev);
        if (m.matches()) {
            final String res = m.group(1);
            final String vol;
            if (m.groupCount() > 2) {
                vol = m.group(2);
            } else {
                vol = "0";
            }
            final ResourceInfo dri = getDrbdResourceNameHash().get(res);
            putDrbdResHash();
            if (dri != null) {
                return dri.getDrbdVolumeInfo(vol);
            }
        }
        return null;
    }

    /**
     * Returns a hash from resource name to drbd resource info hash.
     * Get locks the hash and put unlocks it
     */
    public Map<String, ResourceInfo> getDrbdResourceNameHash() {
        mDrbdResHashLock.lock();
        return drbdResourceNameHash;
    }

    public void putDrbdResHash() {
        mDrbdResHashLock.unlock();
    }

    /** Returns (shallow) copy of all drbdresource info objects. */
    public Iterable<ResourceInfo> getDrbdResHashValues() {
        final Iterable<ResourceInfo> values = new ArrayList<ResourceInfo>(getDrbdResourceNameHash().values());
        putDrbdResHash();
        return values;
    }

    public void reloadAllComboBoxes(final ServiceInfo exceptThisOne) {
        swingUtils.invokeInEdt(new Runnable() {
            @Override
            public void run() {
                lockNameToServiceInfo();
                for (final String name : nameToServiceInfoHash.keySet()) {
                    final Map<String, ServiceInfo> idToInfoHash = nameToServiceInfoHash.get(name);
                    for (final Map.Entry<String, ServiceInfo> serviceEntry : idToInfoHash.entrySet()) {
                        final ServiceInfo si = serviceEntry.getValue();
                        if (si != exceptThisOne) {
                            si.reloadComboBoxes();
                        }
                    }
                }
                unlockNameToServiceInfo();
            }
        });
    }

    /** Returns object that holds data of all VMs. */
    public VmsXml getVmsXml(final Host host) {
        mVmsReadLock.lock();
        try {
            return vmsXML.get(host);
        } finally {
            mVmsReadLock.unlock();
        }
    }

    /**
     * Finds DomainInfo object that contains the VM specified by
     * name.
     */
    public DomainInfo findVMSVirtualDomainInfo(final String name) {
        if (vmsNode != null && name != null) {
            for (final Object info : treeMenuController.nodesToInfos(vmsNode.children())) {
                final DomainInfo domainInfo = (DomainInfo) info;
                if (name.equals(domainInfo.getName())) {
                    return domainInfo;
                }
            }
        }
        return null;
    }

    public ResourceAgentClassInfo getClassInfoMap(final String raClass) {
        return classInfoMap.get(raClass);
    }

    public AvailableServiceInfo getAvailableServiceInfoMap(final ResourceAgent resourceAgent) {
        return availableServiceMap.get(resourceAgent);
    }

    public AvailableServicesInfo getAvailableServicesInfo() {
        return (AvailableServicesInfo) availableServicesNode.getUserObject();
    }

    public ServicesInfo getServicesInfo() {
        return servicesInfo;
    }

    public RscDefaultsInfo getRscDefaultsInfo() {
        return rscDefaultsInfo;
    }

    public void checkAccessOfEverything() {
        servicesInfo.checkResourceFields(null, servicesInfo.getParametersFromXML());
        servicesInfo.updateAdvancedPanels();
        rscDefaultsInfo.updateAdvancedPanels();
        access.updateGlobalItems();
        for (final ServiceInfo si : getExistingServiceList(null)) {
            si.checkResourceFields(null, si.getParametersFromXML());
            si.updateAdvancedPanels();
        }

        globalInfo.checkResourceFields(null, globalInfo.getParametersFromXML());
        globalInfo.updateAdvancedPanels();
        for (final ResourceInfo dri : getDrbdResHashValues()) {
            dri.checkResourceFields(null, dri.getParametersFromXML());
            dri.updateAdvancedPanels();
            dri.updateAllVolumes();
        }

        if (vmsNode != null) {
            for (final Object info : treeMenuController.nodesToInfos(vmsNode.children())) {
                final DomainInfo domainInfo = (DomainInfo) info;
                domainInfo.checkResourceFields(null, domainInfo.getParametersFromXML());
                domainInfo.updateAdvancedPanels();
                for (final Object grandChild : treeMenuController.nodesToInfos(domainInfo.getNode().children())) {
                    final HardwareInfo hardwareInfo = (HardwareInfo) grandChild;
                    hardwareInfo.checkResourceFields(null, hardwareInfo.getParametersFromXML());
                    hardwareInfo.updateAdvancedPanels();
                }
            }
        }

        for (final HbConnectionInfo hbci : crmGraph.getAllHbConnections()) {
            hbci.checkResourceFields(null, hbci.getParametersFromXML());
            hbci.updateAdvancedPanels();
        }

        for (final Host clusterHost : getClusterHosts()) {
            final HostBrowser hostBrowser = clusterHost.getBrowser();
            hostBrowser.getHostInfo().updateAdvancedPanels();
        }
    }

    /** Returns when at least one resource in the list of resources can be
    promoted. */
    public boolean isOneMaster(final Iterable<String> rscs) {
        for (final String id : rscs) {
            mHeartbeatIdToServiceLock();
            final ServiceInfo si = heartbeatIdToServiceInfo.get(id);
            mHeartbeatIdToServiceUnlock();
            if (si == null) {
                continue;
            }
            if (si.getService().isMaster()) {
                return true;
            }
        }
        return false;
    }

    /** Updates host hardware info on all cluster hosts immediately. */
    public void updateHWInfo(final boolean updateLVM) {
        for (final Host h : getClusterHosts()) {
            updateHWInfo(h, updateLVM);
        }
    }

    /** Updates host hardware info immediately. */
    public void updateHWInfo(final Host host, final boolean updateLVM) {
        host.setIsLoading();
        host.getHostParser().getHWInfo(new CategoryInfo[] { clusterHostsInfo },
                new ResourceGraph[] { drbdGraph, crmGraph }, updateLVM);
        swingUtils.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                drbdGraph.addHost(host.getBrowser().getHostDrbdInfo());
            }
        });
        drbdGraph.repaint();
    }

    /** Updates proxy host hardware info immediately. */
    public void updateProxyHWInfo(final Host host) {
        host.setIsLoading();
        host.getHostParser().getHWInfo(new CategoryInfo[] { clusterHostsInfo },
                new ResourceGraph[] { drbdGraph, crmGraph }, !Host.UPDATE_LVM);
        drbdGraph.repaint();
    }

    /** Returns DRBD parameter hash. */
    public Map<Host, String> getHostDrbdParameters() {
        return hostDrbdParameters;
    }

    public void clStatusLock() {
        mCrmStatusLock.lock();
    }

    public void clStatusUnlock() {
        mCrmStatusLock.unlock();
    }

    /** Return null if DRBD info is availble, or the reason why not. */
    public String isDrbdAvailable(final Host host) {
        if (hostDrbdParameters.get(host) == null) {
            return "no suitable man pages";
        }
        return host.isDrbdUtilCompatibleWithDrbdModule();
    }

    public ResourceAgent getGroupResourceAgent() {
        return crmXml.getGroupResourceAgent();
    }

    public void startAnimation(ServiceInfo serviceInfo) {
        crmGraph.startAnimation(serviceInfo);
    }

    public void stopAnimation(ServiceInfo serviceInfo) {
        crmGraph.stopAnimation(serviceInfo);
    }

    public void repaint() {
        crmGraph.repaint();
    }

    public ResourceAgent getCloneResourceAgent() {
        return crmXml.getCloneResourceAgent();
    }

    /** Callback to service menu items, that show ptest results in tooltips. */
    public class ClMenuItemCallback implements ButtonCallback {
        /** Host if over a menu item that belongs to a host. */
        private final Host menuHost;
        private volatile boolean mouseStillOver = false;

        private CallbackAction action;

        public ClMenuItemCallback(final Host menuHost) {
            this.menuHost = menuHost;
        }

        @Override
        public boolean isEnabled() {
            if (clusterStatus == null) {
                return false;
            }
            final Host h;
            if (menuHost == null) {
                h = getDCHost();
            } else {
                h = menuHost;
            }
            return !Tools.versionBeforePacemaker(h);
        }

        /** Mouse out, stops animation. */
        @Override
        public final void mouseOut(final ComponentWithTest component) {
            if (isEnabled()) {
                mouseStillOver = false;
                crmGraph.stopTestAnimation((JComponent) component);
                component.setToolTipText("");
            }
        }

        /** Mouse over, starts animation, calls action() and sets tooltip. */
        @Override
        public final void mouseOver(final ComponentWithTest component) {
            if (isEnabled()) {
                mouseStillOver = true;
                component.setToolTipText(STARTING_PTEST_TOOLTIP);
                component.setToolTipBackground(Tools.getDefaultColor("ClusterBrowser.Test.Tooltip.Background"));
                Tools.sleep(250);
                if (!mouseStillOver) {
                    return;
                }
                mouseStillOver = false;
                final CountDownLatch startTestLatch = new CountDownLatch(1);
                crmGraph.startTestAnimation((JComponent) component, startTestLatch);
                ptestLockAcquire();
                try {
                    clusterStatus.setPtestResult(null);
                    final Host h;
                    if (menuHost == null) {
                        h = getDCHost();
                    } else {
                        h = menuHost;
                    }
                    action.run(h);
                    final PtestData ptestData = new PtestData(CRM.getPtest(h));
                    component.setToolTipText(ptestData.getToolTip());
                    clusterStatus.setPtestResult(ptestData);
                } finally {
                    ptestLockRelease();
                }
                startTestLatch.countDown();
            }
        }

        public ClMenuItemCallback addAction(final CallbackAction action) {
            this.action = action;
            return this;
        }

        ///** Action that is caried out on the host. */
        //protected abstract void action(final Host dcHost);
    }

    /** Callback to service menu items, that show ptest results in tooltips. */
    public class DRBDMenuItemCallback implements ButtonCallback {
        /** Host if over a menu item that belongs to a host. */
        private final Host menuHost;
        private volatile boolean mouseStillOver = false;

        private CallbackAction action;

        public DRBDMenuItemCallback(final Host menuHost) {
            this.menuHost = menuHost;
        }

        @Override
        public final boolean isEnabled() {
            return true;
        }

        /** Mouse out, stops animation. */
        @Override
        public final void mouseOut(final ComponentWithTest component) {
            if (!isEnabled()) {
                return;
            }
            mouseStillOver = false;
            drbdGraph.stopTestAnimation((JComponent) component);
            component.setToolTipText("");
        }

        /** Mouse over, starts animation, calls action() and sets tooltip. */
        @Override
        public final void mouseOver(final ComponentWithTest component) {
            if (!isEnabled()) {
                return;
            }
            mouseStillOver = true;
            component.setToolTipText(Tools.getString("ClusterBrowser.StartingDRBDtest"));
            component.setToolTipBackground(Tools.getDefaultColor("ClusterBrowser.Test.Tooltip.Background"));
            Tools.sleep(250);
            if (!mouseStillOver) {
                return;
            }
            mouseStillOver = false;
            final CountDownLatch startTestLatch = new CountDownLatch(1);
            drbdGraph.startTestAnimation((JComponent) component, startTestLatch);
            drbdtestLockAcquire();
            final Map<Host, String> testOutput = new LinkedHashMap<Host, String>();
            if (menuHost == null) {
                for (final Host h : cluster.getHostsArray()) {
                    action.run(h);
                    testOutput.put(h, DRBD.getDRBDtest());
                }
            } else {
                action.run(menuHost);
                testOutput.put(menuHost, DRBD.getDRBDtest());
            }
            final DRBDtestData dtd = new DRBDtestData(testOutput);
            component.setToolTipText(dtd.getToolTip());
            drbdTestDataLockAcquire();
            drbdtestData = dtd;
            drbdTestDataLockRelease();
            //clusterStatus.setPtestData(ptestData);
            drbdtestLockRelease();
            startTestLatch.countDown();
        }

        ///** Action that is caried out on the host. */
        //protected abstract void action(final Host dcHost);

        public DRBDMenuItemCallback addAction(final CallbackAction action) {
            this.action = action;
            return this;
        }
    }

    public GlobalInfo getGlobalInfo() {
        return globalInfo;
    }
}