/* * This file is part of DRBD Management Console by LINBIT HA-Solutions GmbH * written by Rasto Levrinc. * * Copyright (C) 2009, LINBIT HA-Solutions GmbH. * Copyright (C) 2011-2012, Rastislav Levrinc. * * DRBD Management Console is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your option) * any later version. * * DRBD Management Console is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with drbd; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ package lcmc.lvm.ui; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SpringLayout; import lcmc.cluster.ui.widget.WidgetFactory; import lcmc.common.domain.AccessMode; import lcmc.common.domain.Application; import lcmc.cluster.domain.Cluster; import lcmc.common.ui.utils.SwingUtils; import lcmc.host.domain.Host; import lcmc.common.domain.StringValue; import lcmc.vm.domain.VmsXml; import lcmc.common.domain.Value; import lcmc.drbd.domain.BlockDevice; import lcmc.common.ui.Browser; import lcmc.common.ui.SpringUtilities; import lcmc.drbd.ui.resource.BlockDevInfo; import lcmc.drbd.ui.resource.VolumeInfo; import lcmc.cluster.ui.widget.Widget; import lcmc.lvm.service.LVM; import lcmc.logger.Logger; import lcmc.logger.LoggerFactory; import lcmc.common.ui.utils.MyButton; import lcmc.common.domain.util.Tools; import lcmc.common.ui.utils.WidgetListener; /** * This class implements LVM resize dialog. */ @Named public final class LVResize extends LV { private static final Logger LOG = LoggerFactory.getLogger(LVResize.class); private static final String DESCRIPTION = "Resize the LVM volume. You can make it bigger, but not" + " smaller for now. If this volume is replicated by" + " DRBD, volumes on both nodes will be resized and" + " drbdadm resize will be called. If you have something" + " like filesystem on the DRBD, you have to resize the" + " filesystem yourself."; private static final int RESIZE_LV_TIMEOUT = 5000; private BlockDevInfo blockDevInfo; private Widget sizeWidget; private Widget oldSizeWidget; private Widget maxSizeWidget; private Map<Host, JCheckBox> hostCheckBoxes = null; @Inject private SwingUtils swingUtils; @Inject private WidgetFactory widgetFactory; private MyButton resizeButton; public void init(final BlockDevInfo blockDevInfo) { super.init(null); this.blockDevInfo = blockDevInfo; } @Override protected String getDialogTitle() { return "LVM Resize"; } @Override protected String getDescription() { return DESCRIPTION; } @Override public String cancelButton() { return "Close"; } @Override protected void initDialogBeforeVisible() { super.initDialogBeforeVisible(); enableComponentsLater(new JComponent[]{}); } @Override protected void initDialogAfterVisible() { enableComponents(); if (checkDRBD()) { makeDefaultAndRequestFocusLater(sizeWidget.getComponent()); } } /** Check if it is DRBD device and if it could be resized. */ private boolean checkDRBD() { if (blockDevInfo.getBlockDevice().isDrbd()) { final VolumeInfo dvi = blockDevInfo.getDrbdVolumeInfo(); final BlockDevInfo oBDI = blockDevInfo.getOtherBlockDevInfo(); if (!dvi.isConnected(Application.RunMode.LIVE)) { printErrorAndRetry("Not resizing. DRBD resource is not connected."); sizeWidget.setEnabled(false); resizeButton.setEnabled(false); return false; } else if (dvi.isSyncing()) { printErrorAndRetry("Not resizing. DRBD resource is syncing."); sizeWidget.setEnabled(false); resizeButton.setEnabled(false); return false; } else if (!oBDI.getBlockDevice().isAttached()) { printErrorAndRetry("Not resizing. DRBD resource is not attached on " + oBDI.getHost() + '.'); sizeWidget.setEnabled(false); resizeButton.setEnabled(false); return false; } else if (!blockDevInfo.getBlockDevice().isAttached()) { printErrorAndRetry("Not resizing. DRBD resource is not attached on " + blockDevInfo.getHost() + '.'); sizeWidget.setEnabled(false); resizeButton.setEnabled(false); return false; } else if (!oBDI.getBlockDevice().isPrimary() && !blockDevInfo.getBlockDevice().isPrimary()) { printErrorAndRetry("Not resizing. Must be primary at least on one node."); sizeWidget.setEnabled(false); resizeButton.setEnabled(false); return false; } } return true; } private void enableResizeButton(boolean enable) { if (enable) { final long oldSize = VmsXml.convertToKilobytes(oldSizeWidget.getValue()); final long size = VmsXml.convertToKilobytes(sizeWidget.getValue()); final String maxBlockSize = getMaxBlockSize(); final long maxSize = Long.parseLong(maxBlockSize); maxSizeWidget.setValue(VmsXml.convertKilobytes(maxBlockSize)); if (oldSize >= size || size > maxSize) { enable = false; sizeWidget.wrongValue(); } else { sizeWidget.setBackground(new StringValue(), new StringValue(), true); } } resizeButton.setEnabled(enable); } private void setComboBoxes() { final String oldBlockSize = blockDevInfo.getBlockDevice().getBlockSize(); final String maxBlockSize = getMaxBlockSize(); oldSizeWidget.setValue(VmsXml.convertKilobytes(oldBlockSize)); sizeWidget.setValue(VmsXml.convertKilobytes(Long.toString( (Long.parseLong(oldBlockSize) + Long.parseLong(maxBlockSize)) / 2))); maxSizeWidget.setValue(VmsXml.convertKilobytes(maxBlockSize)); } @Override protected JComponent getInputPane() { resizeButton = widgetFactory.createButton("Resize"); resizeButton.setEnabled(false); final JPanel pane = new JPanel(new SpringLayout()); final JPanel inputPane = new JPanel(new SpringLayout()); inputPane.setBackground(Browser.BUTTON_PANEL_BACKGROUND); /* old size */ final JLabel oldSizeLabel = new JLabel("Current Size"); oldSizeLabel.setEnabled(false); final String oldBlockSize = blockDevInfo.getBlockDevice().getBlockSize(); oldSizeWidget = widgetFactory.createInstance( Widget.Type.TEXTFIELDWITHUNIT, VmsXml.convertKilobytes(oldBlockSize), Widget.NO_ITEMS, getUnits(), Widget.NO_REGEXP, 250, Widget.NO_ABBRV, new AccessMode(AccessMode.OP, AccessMode.NORMAL), Widget.NO_BUTTON); oldSizeWidget.setEnabled(false); inputPane.add(oldSizeLabel); inputPane.add(oldSizeWidget.getComponent()); inputPane.add(new JLabel()); final String maxBlockSize = getMaxBlockSize(); /* size */ final String newBlockSize = Long.toString((Long.parseLong(oldBlockSize) + Long.parseLong(maxBlockSize)) / 2); final JLabel sizeLabel = new JLabel("New Size"); sizeWidget = widgetFactory.createInstance( Widget.Type.TEXTFIELDWITHUNIT, VmsXml.convertKilobytes(newBlockSize), Widget.NO_ITEMS, getUnits(), Widget.NO_REGEXP, 250, Widget.NO_ABBRV, new AccessMode(AccessMode.OP, AccessMode.NORMAL), Widget.NO_BUTTON); inputPane.add(sizeLabel); inputPane.add(sizeWidget.getComponent()); resizeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final Thread thread = new Thread(new Runnable() { @Override public void run() { if (checkDRBD()) { swingUtils.invokeAndWait(new Runnable() { @Override public void run() { enableResizeButton(false); } }); disableComponents(); getProgressBar().start(RESIZE_LV_TIMEOUT * hostCheckBoxes.size()); final boolean ret = lvAndDrbdResize(sizeWidget.getStringValue()); final Host host = blockDevInfo.getHost(); host.getBrowser().getClusterBrowser().updateHWInfo(host, Host.UPDATE_LVM); setComboBoxes(); if (ret) { progressBarDone(); } else { progressBarDoneError(); } enableComponents(); } } }); thread.start(); } }); inputPane.add(resizeButton); /* max size */ final JLabel maxSizeLabel = new JLabel("Max Size"); maxSizeLabel.setEnabled(false); maxSizeWidget = widgetFactory.createInstance( Widget.Type.TEXTFIELDWITHUNIT, VmsXml.convertKilobytes(maxBlockSize), Widget.NO_ITEMS, getUnits(), Widget.NO_REGEXP, 250, Widget.NO_ABBRV, new AccessMode(AccessMode.OP, AccessMode.NORMAL), Widget.NO_BUTTON); maxSizeWidget.setEnabled(false); inputPane.add(maxSizeLabel); inputPane.add(maxSizeWidget.getComponent()); inputPane.add(new JLabel()); sizeWidget.addListeners(new WidgetListener() { @Override public void check(final Value value) { enableResizeButton(true); } }); SpringUtilities.makeCompactGrid(inputPane, 3, 3, /* rows, cols */ 1, 1, /* initX, initY */ 1, 1); /* xPad, yPad */ pane.add(inputPane); final JPanel hostsPane = new JPanel(new FlowLayout(FlowLayout.LEADING)); final Cluster cluster = blockDevInfo.getHost().getCluster(); hostCheckBoxes = Tools.getHostCheckBoxes(cluster); hostsPane.add(new JLabel("Select Hosts: ")); final Host host = blockDevInfo.getHost(); final String lv = blockDevInfo.getBlockDevice().getLogicalVolume(); for (final Map.Entry<Host, JCheckBox> hostEntry : hostCheckBoxes.entrySet()) { final Set<String> allLVS = hostEntry.getKey().getHostParser().getAllLogicalVolumes(); hostEntry.getValue().addItemListener( new ItemListener() { @Override public void itemStateChanged(final ItemEvent e) { enableResizeButton(true); } }); if (host == hostEntry.getKey() || blockDevInfo.getBlockDevice().isDrbd() && blockDevInfo.getOtherBlockDevInfo().getHost() == hostEntry.getKey()) { hostEntry.getValue().setEnabled(false); hostEntry.getValue().setSelected(true); } else if (!blockDevInfo.getBlockDevice().isDrbd() && !allLVS.contains(lv)) { hostEntry.getValue().setEnabled(false); hostEntry.getValue().setSelected(false); } else { hostEntry.getValue().setEnabled(true); hostEntry.getValue().setSelected(false); } hostsPane.add(hostEntry.getValue()); } final JScrollPane sp = new JScrollPane(hostsPane); sp.setPreferredSize(new Dimension(0, 45)); pane.add(sp); pane.add(getProgressBarPane(null)); pane.add(getAnswerPane("")); SpringUtilities.makeCompactGrid(pane, 4, 1, /* rows, cols */ 0, 0, /* initX, initY */ 0, 0); /* xPad, yPad */ enableResizeButton(true); return pane; } private boolean lvAndDrbdResize(final String size) { final boolean ret = LVM.resize(blockDevInfo.getHost(), blockDevInfo.getBlockDevice().getName(), size, Application.RunMode.LIVE); if (ret) { answerPaneSetText("Logical volume was successfully resized on " + blockDevInfo.getHost() + '.'); /* resize lvm volume on the other node. */ final String lvm = blockDevInfo.getBlockDevice().getName(); final BlockDevInfo oBDI = blockDevInfo.getOtherBlockDevInfo(); boolean resizingFailed = false; for (final Map.Entry<Host, JCheckBox> hostEntry : hostCheckBoxes.entrySet()) { if (hostEntry.getKey() == blockDevInfo.getHost() || !hostEntry.getValue().isSelected()) { continue; } for (final BlockDevice b : hostEntry.getKey().getBlockDevices()) { if (lvm.equals(b.getName()) || (oBDI != null && oBDI.getBlockDevice() == b)) { /* drbd or selected other host */ final boolean oRet = LVM.resize(hostEntry.getKey(), b.getName(), size, Application.RunMode.LIVE); if (oRet) { answerPaneAddText("Logical volume was successfully" + " resized on " + hostEntry.getKey().getName() + '.'); } else { answerPaneAddTextError("Resizing of " + b.getName() + " on host " + hostEntry.getKey().getName() + " failed."); resizingFailed = true; } break; } if (resizingFailed) { break; } } } if (oBDI != null && !resizingFailed) { final boolean dRet = blockDevInfo.resizeDrbd(Application.RunMode.LIVE); if (dRet) { answerPaneAddText("DRBD resource " + blockDevInfo.getDrbdVolumeInfo().getName() + " was successfully resized."); } else { answerPaneAddTextError("DRBD resource " + blockDevInfo.getDrbdVolumeInfo().getName() + " resizing failed."); } } } else { answerPaneAddTextError("Resizing of " + blockDevInfo.getName() + " on host " + blockDevInfo.getHost() + " failed."); } return ret; } /** Returns maximum block size available in the group. */ private String getMaxBlockSize() { final long free = blockDevInfo.getFreeInVolumeGroup() / 1024; String maxBlockSize = "0"; try { final long taken = Long.parseLong(blockDevInfo.getBlockDevice().getBlockSize()); final BlockDevInfo oBDI = blockDevInfo.getOtherBlockDevInfo(); long max = free + taken; final String lvm = blockDevInfo.getBlockDevice().getName(); if (hostCheckBoxes != null) { for (final Map.Entry<Host, JCheckBox> hostEntry : hostCheckBoxes.entrySet()) { if (blockDevInfo.getHost() == hostEntry.getKey()) { continue; } if (hostEntry.getValue().isSelected()) { for (final BlockDevice b : hostEntry.getKey().getBlockDevices()) { if (lvm.equals(b.getName()) || (oBDI != null && oBDI.getBlockDevice() == b)) { final long oFree = hostEntry.getKey().getHostParser().getFreeInVolumeGroup(b.getVolumeGroup()) / 1024; final long oTaken = Long.parseLong(b.getBlockSize()); if (oFree + oTaken < max) { /* take the smaller maximum. */ max = oFree + oTaken; } } } } } } maxBlockSize = Long.toString(max); } catch (final NumberFormatException e) { LOG.appWarning("getMaxBlockSize: could not get max size"); /* ignore */ } return maxBlockSize; } }