/* * This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH * written by Rasto Levrinc. * * Copyright (C) 2009-2010, LINBIT HA-Solutions GmbH. * Copyright (C) 2009-2010, Rasto Levrinc * * DRBD Management Console is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your option) * any later version. * * DRBD Management Console is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with drbd; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ package lcmc.crm.ui.resource; import com.google.common.base.Optional; import lcmc.cluster.ui.ClusterBrowser; import lcmc.cluster.ui.widget.Check; import lcmc.cluster.ui.widget.Widget; import lcmc.common.domain.AccessMode; import lcmc.common.domain.Application; import lcmc.common.domain.ResourceValue; import lcmc.common.domain.StringValue; import lcmc.common.domain.Value; import lcmc.common.domain.util.Tools; import lcmc.common.ui.Browser; import lcmc.common.ui.EditableInfo; import lcmc.common.ui.Info; 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.Dialogs; import lcmc.common.ui.utils.SwingUtils; import lcmc.common.ui.utils.UpdatableItem; import lcmc.crm.domain.ClusterStatus; import lcmc.crm.domain.CrmXml; import lcmc.crm.domain.PtestData; import lcmc.crm.domain.ResourceAgent; import lcmc.crm.service.CRM; import lcmc.crm.ui.CrmGraph; import lcmc.host.domain.Host; import lcmc.logger.Logger; import lcmc.logger.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; /** * This class holds info data for services view and global heartbeat * config. */ @Named public class ServicesInfo extends EditableInfo { private static final Logger LOG = LoggerFactory.getLogger(ServicesInfo.class); static final ImageIcon CLUSTER_ICON = Tools.createImageIcon(Tools.getDefault("ClustersPanel.ClusterIcon")); /** Cache for the info panel. */ private JComponent infoPanel = null; @Inject private ServicesMenu servicesMenu; @Inject private ProgressIndicator progressIndicator; @Inject private Application application; @Inject private SwingUtils swingUtils; @Inject private TreeMenuController treeMenuController; @Inject private CrmServiceFactory crmServiceFactory; @Inject private Dialogs dialogs; public void einit(final String name, final Browser browser) { super.einit(Optional.of(new ResourceValue(name)), name, browser); } @Override public ClusterBrowser getBrowser() { return (ClusterBrowser) super.getBrowser(); } void setInfoPanel(final JComponent infoPanel) { this.infoPanel = infoPanel; } /** Returns names of all global parameters. */ @Override public String[] getParametersFromXML() { final CrmXml crmXml = getBrowser().getCrmXml(); if (crmXml == null) { return null; } return crmXml.getGlobalParameters(); } /** * Returns long description of the global parameter, that is used for * tool tips. */ @Override protected String getParamLongDesc(final String param) { return getBrowser().getCrmXml().getGlobalLongDesc(param); } /** * Returns short description of the global parameter, that is used as * label. */ @Override protected String getParamShortDesc(final String param) { return getBrowser().getCrmXml().getGlobalShortDesc(param); } @Override protected Value getParamDefault(final String param) { return getBrowser().getCrmXml().getGlobalParamDefault(param); } @Override protected Value getParamPreferred(final String param) { return getBrowser().getCrmXml().getGlobalPreferredValue(param); } @Override protected Value[] getParamPossibleChoices(final String param) { return getBrowser().getCrmXml().getGlobalComboBoxChoices(param); } @Override protected boolean checkParam(final String param, final Value newValue) { return getBrowser().getCrmXml().checkGlobalParam(param, newValue); } @Override protected boolean isInteger(final String param) { return getBrowser().getCrmXml().isGlobalInteger(param); } @Override protected boolean isLabel(final String param) { return getBrowser().getCrmXml().isGlobalLabel(param); } @Override protected boolean isTimeType(final String param) { return getBrowser().getCrmXml().isGlobalTimeType(param); } @Override protected boolean isAdvanced(final String param) { return Tools.areEqual(getParamDefault(param), getParamSaved(param)) && getBrowser().getCrmXml().isGlobalParamAdvanced(param); } @Override protected AccessMode.Type getAccessType(final String param) { return getBrowser().getCrmXml().getGlobalParamAccessType(param); } @Override protected String isEnabled(final String param) { return null; } @Override protected AccessMode.Mode isEnabledOnlyInAdvancedMode(final String param) { return AccessMode.NORMAL; } @Override protected boolean isRequired(final String param) { return getBrowser().getCrmXml().isGlobalRequired(param); } /** * Returns whether the global parameter is of boolean type and * requires a checkbox. */ @Override protected boolean isCheckBox(final String param) { return getBrowser().getCrmXml().isGlobalBoolean(param); } @Override protected String getParamType(final String param) { return getBrowser().getCrmXml().getGlobalType(param); } @Override protected String getSection(final String param) { return getBrowser().getCrmXml().getGlobalSectionForDisplay(param); } void apply(final Host dcHost, final Application.RunMode runMode) { LOG.debug1("apply: start: test: " + runMode); final String[] params = getParametersFromXML(); if (Application.isLive(runMode)) { swingUtils.invokeAndWait(new Runnable() { @Override public void run() { getApplyButton().setEnabled(false); getRevertButton().setEnabled(false); getApplyButton().setToolTipText(""); } }); } getInfoPanel(); waitForInfoPanel(); /* update pacemaker */ final Map<String, String> args = new HashMap<String, String>(); for (final String param : params) { final Value value = getComboBoxValue(param); if (Tools.areEqual(value, getParamDefault(param))) { continue; } if (value == null || value.isNothingSelected()) { continue; } args.put(param, value.getValueForConfig()); } final RscDefaultsInfo rdi = getBrowser().getRscDefaultsInfo(); final String[] rdiParams = rdi.getParametersFromXML(); final Map<String, String> rdiMetaArgs = new LinkedHashMap<String, String>(); for (final String param : rdiParams) { final Value value = rdi.getComboBoxValue(param); if (Tools.areEqual(value, rdi.getParamDefault(param))) { continue; } if (value != null && !value.isNothingSelected()) { rdiMetaArgs.put(param, value.getValueForConfig()); } } final String rscDefaultsId = getBrowser().getClusterStatus().getRscDefaultsId(runMode); CRM.setGlobalParameters(dcHost, args, rdiMetaArgs, rscDefaultsId, runMode); if (Application.isLive(runMode)) { storeComboBoxValues(params); rdi.storeComboBoxValues(rdiParams); } for (final ServiceInfo si : getBrowser().getExistingServiceList(null)) { final Check check = si.checkResourceFields(null, si.getParametersFromXML(), true, false, false); if (check.isCorrect() && check.isChanged()) { si.apply(dcHost, runMode); } } if (Application.isLive(runMode)) { setApplyButtons(null, params); } LOG.debug1("apply: end: test: " + runMode); } /** Sets heartbeat global parameters after they were obtained. */ public void setGlobalConfig(final ClusterStatus clStatus) { final String[] params = getParametersFromXML(); for (final String param : params) { final String valueS = clStatus.getGlobalParam(param); if (valueS == null) { continue; } final Value value = new StringValue(valueS); final Value oldValue = getParamSaved(param); if (!Tools.areEqual(value, oldValue)) { getResource().setValue(param, value); final Widget wi = getWidget(param, null); if (wi != null) { wi.setValue(value); } } } if (infoPanel == null) { swingUtils.invokeLater(new Runnable() { @Override public void run() { getInfoPanel(); } }); } } /** Clears the info panel cache, forcing it to reload. */ @Override public boolean selectAutomaticallyInTreeMenu() { return infoPanel == null; } /** Returns type of the info text. text/plain or text/html. */ @Override protected String getInfoMimeType() { return MainData.MIME_TYPE_TEXT_HTML; } /** * Returns info for info panel, that hb status failed or null, in which * case the getInfoPanel() function will show. */ @Override public String getInfo() { if (getBrowser().crmStatusFailed()) { return Tools.getString("ClusterBrowser.ClStatusFailed"); } return null; } /** Creates rsc_defaults panel. */ private void addRscDefaultsPanel(final JPanel optionsPanel, final int leftWidth, final int rightWidth) { final RscDefaultsInfo rdi = getBrowser().getRscDefaultsInfo(); rdi.widgetClear(); final String[] params = rdi.getParametersFromXML(); rdi.addParams(optionsPanel, params, leftWidth, rightWidth, null); } /** Returns editable info panel for global crm config. */ @Override public JComponent getInfoPanel() { /* if don't have hb status we don't have all the info we need here. * TODO: OR we need to get hb status only once */ if (getBrowser().crmStatusFailed()) { return super.getInfoPanel(); } final CrmGraph crmGraph = getBrowser().getCrmGraph(); if (infoPanel != null) { crmGraph.pickBackground(); return infoPanel; } final JPanel newPanel = new JPanel(); newPanel.setBackground(ClusterBrowser.PANEL_BACKGROUND); newPanel.setLayout(new BoxLayout(newPanel, BoxLayout.PAGE_AXIS)); if (getBrowser().getCrmXml() == null || getBrowser().getClusterStatus() == null) { return newPanel; } final ButtonCallback buttonCallback = new ButtonCallback() { private volatile boolean mouseStillOver = false; /** * Whether the whole thing should be enabled. */ @Override public boolean isEnabled() { final Host dcHost = getBrowser().getDCHost(); return dcHost != null && !Tools.versionBeforePacemaker(dcHost); } @Override public void mouseOut(final ComponentWithTest component) { if (!isEnabled()) { return; } mouseStillOver = false; crmGraph.stopTestAnimation((JComponent) component); component.setToolTipText(""); } @Override public void mouseOver(final ComponentWithTest component) { if (!isEnabled()) { return; } mouseStillOver = true; component.setToolTipText(ClusterBrowser.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); final Host dcHost = getBrowser().getDCHost(); getBrowser().ptestLockAcquire(); try { final ClusterStatus clStatus = getBrowser().getClusterStatus(); clStatus.setPtestResult(null); apply(dcHost, Application.RunMode.TEST); final PtestData ptestData = new PtestData(CRM.getPtest(dcHost)); component.setToolTipText(ptestData.getToolTip()); clStatus.setPtestResult(ptestData); } finally { getBrowser().ptestLockRelease(); } startTestLatch.countDown(); } }; initCommitButton(buttonCallback); getBrowser().getRscDefaultsInfo().setApplyButton(getApplyButton()); getBrowser().getRscDefaultsInfo().setRevertButton(getRevertButton()); final JPanel mainPanel = new JPanel(); mainPanel.setBackground(ClusterBrowser.PANEL_BACKGROUND); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS)); final JPanel optionsPanel = new JPanel(); optionsPanel.setBackground(ClusterBrowser.PANEL_BACKGROUND); optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.PAGE_AXIS)); optionsPanel.setAlignmentX(java.awt.Component.LEFT_ALIGNMENT); final JPanel buttonPanel = new JPanel(new BorderLayout()); buttonPanel.setBackground(ClusterBrowser.BUTTON_PANEL_BACKGROUND); buttonPanel.setMinimumSize(new Dimension(0, 50)); buttonPanel.setPreferredSize(new Dimension(0, 50)); buttonPanel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50)); buttonPanel.add(getActionsButton(), BorderLayout.LINE_END); newPanel.add(buttonPanel); final String[] params = getParametersFromXML(); addParams(optionsPanel, params, application.getDefaultSize("ClusterBrowser.DrbdResLabelWidth"), application.getDefaultSize("ClusterBrowser.DrbdResFieldWidth"), null); addRscDefaultsPanel(optionsPanel, application.getDefaultSize("ClusterBrowser.DrbdResLabelWidth"), application.getDefaultSize("ClusterBrowser.DrbdResFieldWidth")); getApplyButton().addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { LOG.debug1("actionPerformed: BUTTON: apply"); final Thread thread = new Thread( new Runnable() { @Override public void run() { getBrowser().clStatusLock(); apply(getBrowser().getDCHost(), Application.RunMode.LIVE); getBrowser().clStatusUnlock(); } } ); thread.start(); } } ); getRevertButton().addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { LOG.debug1("actionPerformed: BUTTON: revert"); final Thread thread = new Thread(new Runnable() { @Override public void run() { getBrowser().clStatusLock(); revert(); getBrowser().clStatusUnlock(); } }); thread.start(); } } ); /* apply button */ addApplyButton(buttonPanel); addRevertButton(buttonPanel); swingUtils.invokeLater(new Runnable() { @Override public void run() { setApplyButtons(null, params); } }); mainPanel.add(optionsPanel); newPanel.add(getMoreOptionsPanel(application.getServiceLabelWidth() + application.getServiceFieldWidth() + 4)); newPanel.add(new JScrollPane(mainPanel)); crmGraph.pickBackground(); infoPanel = newPanel; infoPanelDone(); return infoPanel; } /** Returns heartbeat graph. */ @Override public JPanel getGraphicalView() { return getBrowser().getCrmGraph().getGraphPanel(); } /** * Adds service to the list of services. * TODO: are they both used? */ public ServiceInfo addServicePanel(final ResourceAgent newResourceAgent, final Point2D pos, final boolean reloadNode, final String heartbeatId, final CloneInfo newCi, final Application.RunMode runMode) { final ServiceInfo newServiceInfo = crmServiceFactory.createFromResourceAgent( newResourceAgent, getBrowser().getClusterStatus().isMaster(heartbeatId), getBrowser()); if (heartbeatId != null) { newServiceInfo.getService().setCrmId(heartbeatId); getBrowser().addToHeartbeatIdList(newServiceInfo); } if (newCi == null) { addServicePanel(newServiceInfo, pos, reloadNode, true, runMode); } return newServiceInfo; } /** * Adds new service to the specified position. If position is null, it * will be computed later. reloadNode specifies if the node in * the menu should be reloaded and get uptodate. */ public void addServicePanel(final ServiceInfo newServiceInfo, final Point2D pos, final boolean reloadNode, final boolean interactive, final Application.RunMode runMode) { swingUtils.invokeAndWait(new Runnable() { @Override public void run() { newServiceInfo.getService().setResourceClass(newServiceInfo.getResourceAgent().getResourceClass()); final CrmGraph hg = getBrowser().getCrmGraph(); getBrowser().addNameToServiceInfoHash(newServiceInfo); if (!hg.addResource(newServiceInfo, null, pos, false, /* colocation only */ false, /* order only */ runMode)) { final DefaultMutableTreeNode newServiceNode = treeMenuController.createMenuItem( getBrowser().getServicesNode(), newServiceInfo); if (interactive) { if (newServiceInfo.getResourceAgent().isProbablyMasterSlave()) { /* only if it was added manually. */ newServiceInfo.changeType(ServiceInfo.MASTER_SLAVE_TYPE_STRING); } else if (newServiceInfo.getResourceAgent().isProbablyClone()) { newServiceInfo.changeType(ServiceInfo.CLONE_TYPE_STRING); } } if (reloadNode) { /* show it */ treeMenuController.reloadNode(getBrowser().getServicesNode(), false); treeMenuController.reloadNode(newServiceNode, true); } getBrowser().reloadAllComboBoxes(newServiceInfo); hg.scale(); } hg.reloadServiceMenus(); } }); } /** Returns 'add service' list for graph popup menu. */ List<ResourceAgent> getAddServiceList(final String cl) { return getBrowser().globalGetAddServiceList(cl); } /** * Returns background popup. Click on background represents cluster as * whole. */ @Override public List<UpdatableItem> createPopup() { return servicesMenu.getPulldownMenu(this); } /** * Returns whether all the parameters are correct. If param is null, * all parameters will be checked, otherwise only the param, but other * parameters will be checked only in the cache. This is good if only * one value is changed and we don't want to check everything. */ @Override public Check checkResourceFields(final String param, final String[] params) { final RscDefaultsInfo rdi = getBrowser().getRscDefaultsInfo(); final Check check = new Check(new ArrayList<String>(), new ArrayList<String>()); check.addCheck(rdi.checkResourceFields(param, rdi.getParametersFromXML(), true)); check.addCheck(super.checkResourceFields(param, params)); for (final ServiceInfo si : getBrowser().getExistingServiceList(null)) { check.addCheck(si.checkResourceFields(null, si.getParametersFromXML(), true, false, false)); } return check; } /** Revert all values. */ @Override public void revert() { super.revert(); final RscDefaultsInfo rdi = getBrowser().getRscDefaultsInfo(); rdi.revert(); for (final ServiceInfo si : getBrowser().getExistingServiceList(null)) { if (si.checkResourceFields(null, si.getParametersFromXML(), true, false, false).isChanged()) { si.revert(); } } //TODO: should remove new resources and constraints } /** * Copy/paste field from one field to another. */ private void copyPasteField(final Widget oldWi, final Widget newWi) { if (newWi == null || oldWi == null) { return; } final Value oldValue = oldWi.getValue(); swingUtils.invokeLater(new Runnable() { @Override public void run() { if (oldValue == null || oldValue.isNothingSelected()) { newWi.setValueNoListeners(null); } else { newWi.setValueNoListeners(oldValue); } } }); } private void copyPasteFields(final ServiceInfo oldSi, final ServiceInfo newSi) { /* parameters */ for (final String param : oldSi.getParametersFromXML()) { if (ServiceInfo.GUI_ID.equals(param) || ServiceInfo.PCMK_ID.equals(param)) { if (getBrowser().isCrmId(oldSi.getService().getCrmId())) { continue; } } copyPasteField(oldSi.getWidget(param, null), newSi.getWidget(param, null)); } /* operations */ copyPasteField(oldSi.getSameAsOperationsWi(), newSi.getSameAsOperationsWi()); for (final String op : oldSi.getResourceAgent().getOperationNames()) { for (final String param : getBrowser().getCrmOperationParams(op)) { copyPasteField(oldSi.getOperationsComboBox(op, param), newSi.getOperationsComboBox(op, param)); } } /* locations */ for (final Host host : getBrowser().getClusterHosts()) { final HostInfo hi = host.getBrowser().getHostInfo(); copyPasteField(oldSi.getScoreComboBoxHash().get(hi), newSi.getScoreComboBoxHash().get(hi)); } /* ping */ copyPasteField(oldSi.getPingComboBox(), newSi.getPingComboBox()); } public void pasteServices(final List<Info> oldInfos) { if (oldInfos.isEmpty()) { return; } final String cn = getBrowser().getCluster().getName(); progressIndicator.startProgressIndicator(cn, "paste"); final ClusterBrowser otherBrowser = (ClusterBrowser) oldInfos.get(0).getBrowser(); getBrowser().getClusterViewPanel().setDisabledDuringLoad(true); otherBrowser.getClusterViewPanel().setDisabledDuringLoad(true); for (Info oldI : oldInfos) { CloneInfo oci = null; if (oldI instanceof CloneInfo) { oci = (CloneInfo) oldI; oldI = oci.getContainedService(); } final CloneInfo oldCi = oci; if (oldI instanceof ServiceInfo) { final ServiceInfo oldSi = (ServiceInfo) oldI; final ServiceInfo newSi = addServicePanel(oldSi.getResourceAgent(), null, /* pos */ true, null, /* clone id */ null, Application.RunMode.LIVE); swingUtils.invokeLater(new Runnable() { @Override public void run() { if (!(newSi instanceof CloneInfo)) { oldSi.getInfoPanel(); newSi.getInfoPanel(); oldSi.waitForInfoPanel(); newSi.waitForInfoPanel(); } if (oldCi != null) { final Value v = newSi.getTypeRadioGroup().getValue(); if (oldCi.getService().isMaster()) { if (!ServiceInfo.MASTER_SLAVE_TYPE_STRING.equals(v)) { newSi.getTypeRadioGroup().setValue(ServiceInfo.MASTER_SLAVE_TYPE_STRING); } } else { if (!ServiceInfo.CLONE_TYPE_STRING.equals(v)) { newSi.getTypeRadioGroup().setValue(ServiceInfo.CLONE_TYPE_STRING); } } } copyPasteFields(oldSi, newSi); } }); /* clone parameters */ final CloneInfo newCi = newSi.getCloneInfo(); if (newCi != null) { for (final String param : oldCi.getParametersFromXML()) { if (ServiceInfo.GUI_ID.equals(param) || ServiceInfo.PCMK_ID.equals(param)) { if (getBrowser().isCrmId(oldCi.getService().getCrmId())) { continue; } } swingUtils.invokeLater(new Runnable() { @Override public void run() { copyPasteField(oldCi.getWidget(param, null), newCi.getWidget(param, null)); } }); } } if (oldI instanceof GroupInfo) { final GroupInfo oldGi = (GroupInfo) oldI; final GroupInfo newGi = (GroupInfo) newSi; swingUtils.invokeInEdt(new Runnable() { @Override public void run() { for (final ServiceInfo oldChild : oldGi.getSubServices()) { oldChild.getInfoPanel(); final ServiceInfo newChild = newGi.addGroupServicePanel(oldChild.getResourceAgent(), false); newChild.getInfoPanel(); copyPasteFields(oldChild, newChild); } treeMenuController.reloadNode(newGi.getNode(), false); } }); } } } progressIndicator.stopProgressIndicator(cn, "paste"); otherBrowser.getClusterViewPanel().setDisabledDuringLoad(false); getBrowser().getClusterViewPanel().setDisabledDuringLoad(false); } public void exportGraphAsPng() { final Optional<String> savePath = dialogs.getFileName("lcmc-pcmk"); if (savePath.isPresent()) { new Thread() { public void run() { BufferedImage image = getBrowser().getCrmGraph().createImage(); Tools.writeImage(savePath.get(), image, "PNG"); } }.start(); } } public void cleanupServiceMenu(final List<ServiceInfo> groupServiceIsPresent) { for (final Object info : treeMenuController.nodesToInfos(getNode().children())) { final ServiceInfo serviceInfo = (ServiceInfo) info; for (final ServiceInfo subService : serviceInfo.getSubServices()) { if (!groupServiceIsPresent.contains(subService) && !subService.getService().isNew()) { //TODO: NPE /* remove the group service from the menu that does not exist anymore. */ subService.removeInfo(); } } } } public void reloadNode() { treeMenuController.reloadNode(getBrowser().getServicesNode(), false); } public void moveNodeToPosition(int pos, DefaultMutableTreeNode node) { treeMenuController.moveNodeToPosition(node, pos); } }