diff options
author | wiktor w brodlo <wiktor@brodlo.net> | 2011-06-15 16:59:54 +0000 |
---|---|---|
committer | wiktor w brodlo <wiktor@brodlo.net> | 2011-06-15 16:59:54 +0000 |
commit | 2590d96369d0217e31dc2812690dde61dac417b5 (patch) | |
tree | 82276f787b08a28548e342c7921486f1acefab9f /iw/lvm_dialog_gui.py | |
parent | first commit (diff) | |
download | anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.gz anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.bz2 anaconda-2590d96369d0217e31dc2812690dde61dac417b5.zip |
Initial import from Sabayon (ver 0.9.9.56)
Diffstat (limited to 'iw/lvm_dialog_gui.py')
-rw-r--r-- | iw/lvm_dialog_gui.py | 1464 |
1 files changed, 1464 insertions, 0 deletions
diff --git a/iw/lvm_dialog_gui.py b/iw/lvm_dialog_gui.py new file mode 100644 index 0000000..0a9785f --- /dev/null +++ b/iw/lvm_dialog_gui.py @@ -0,0 +1,1464 @@ +# +# lvm_dialog_gui.py: dialog for editing a volume group request +# +# Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc. +# All rights reserved. +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author(s): Michael Fulbright <msf@redhat.com> +# + +import copy + +import gobject +import gtk +import datacombo + +import gui +from partition_ui_helpers_gui import * +from constants import * +from storage.devices import * +from storage.deviceaction import * + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) +P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z) + +import logging +log = logging.getLogger("anaconda") + +class VolumeGroupEditor: + + def getTempVG(self): + pvs = [copy.deepcopy(pv) for pv in self.pvs] + vg = LVMVolumeGroupDevice('tmp-%s' % self.vg.name, + parents=pvs, peSize=self.peSize) + for lv in self.lvs.values(): + _l = LVMLogicalVolumeDevice(lv['name'], vg, format=lv['format'], + size=lv['size'], exists=lv['exists'], + stripes=lv['stripes'], + logSize=lv['logSize'], + snapshotSpace=lv['snapshotSpace']) + _l.originalFormat = lv['originalFormat'] + + return vg + + def numAvailableLVSlots(self): + return max(0, lvm.MAX_LV_SLOTS - len(self.lvs)) + + def computeSpaceValues(self): + vg = self.getTempVG() + vgsize = vg.size + vgfree = vg.freeSpace + vgused = vgsize - vgfree + return (vgsize, vgused, vgfree) + + def getPVWastedRatio(self, newpe): + """ given a new pe value, return percentage of smallest PV wasted + + newpe - (int) new value of PE, in KB + """ + pvlist = self.getSelectedPhysicalVolumes() + + waste = 0.0 + for pv in pvlist: + waste = max(waste, (long(pv.size*1024) % newpe)/(pv.size*1024.0)) + + return waste + + def getSmallestPVSize(self): + """ finds the smallest PV and returns its size in MB + """ + first = 1 + pvlist = self.getSelectedPhysicalVolumes() + for pv in pvlist: + try: + pesize = int(self.peCombo.get_active_value()) / 1024.0 + except: + pesize = self.vg.peSize + + # FIXME: move this logic into a property of LVMVolumeGroupDevice + pvsize = max(0, lvm.clampSize(pv.size, pesize) - pesize) + if first: + minpvsize = pvsize + first = 0 + else: + minpvsize = min(pvsize, minpvsize) + + return minpvsize + + + def reclampLV(self, newpe): + """ given a new pe value, set logical volume sizes accordingly + + newpe - (int) new value of PE, in MB + """ + + pvlist = self.getSelectedPhysicalVolumes() + availSpaceMB = self.computeVGSize(pvlist, newpe) + + # see if total space is enough + used = 0 + resize = False + for lv in self.lvs.values(): + # total space required by an lv may be greater than lv size. + vg_space = lv['size'] * lv['stripes'] + lv['logSize'] \ + + lv['snapshotSpace'] + clamped_vg_space = lvm.clampSize(vg_space, newpe, roundup=1) + used += clamped_vg_space + if lv['size'] != lvm.clampSize(lv['size'], newpe, roundup=1): + resize = True + + if used > availSpaceMB: + self.intf.messageWindow(_("Not enough space"), + _("The physical extent size cannot be " + "changed because otherwise the space " + "required by the currently defined " + "logical volumes will be increased " + "to more than the available space."), + custom_icon="error") + return 0 + + if resize: + rc = self.intf.messageWindow(_("Confirm Physical Extent Change"), + _("This change in the value of the " + "physical extent will require the " + "sizes of the current logical " + "volume requests to be rounded " + "up in size to an integer multiple " + "of the " + "physical extent.\n\nThis change " + "will take effect immediately."), + type="custom", custom_icon="question", + custom_buttons=["gtk-cancel", _("C_ontinue")]) + if not rc: + return 0 + + for lv in self.lvs.values(): + lv['size'] = lvm.clampSize(lv['size'], newpe, roundup=1) + + return 1 + + def peChangeCB(self, widget, *args): + """ handle changes in the Physical Extent option menu + + widget - menu item which was activated + peOption - the Option menu containing the items. The data value for + "lastval" is the previous PE value. + """ + + curval = int(widget.get_active_value()) + # this one's in MB so we can stop with all this dividing by 1024 + curpe = curval / 1024.0 + lastval = widget.get_data("lastpe") + lastidx = widget.get_data("lastidx") + + # see if PE is too large compared to smallest PV + maxpvsize = self.getSmallestPVSize() + if curpe > maxpvsize: + self.intf.messageWindow(_("Not enough space"), + _("The physical extent size cannot be " + "changed because the value selected " + "(%(curpe)10.2f MB) is larger than the " + "smallest physical volume " + "(%(maxpvsize)10.2f MB) in the volume " + "group.") % {'curpe': curpe, + 'maxpvsize': maxpvsize}, + custom_icon="error") + widget.set_active(lastidx) + return 0 + + # see if new PE will make any PV useless due to overhead + if lvm.clampSize(maxpvsize, curpe) < curpe: + self.intf.messageWindow(_("Not enough space"), + _("The physical extent size cannot be " + "changed because the value selected " + "(%(curpe)10.2f MB) is too large " + "compared to the size of the " + "smallest physical volume " + "(%(maxpvsize)10.2f MB) in the " + "volume group.") + % {'curpe': curpe, 'maxpvsize': maxpvsize}, + custom_icon="error") + widget.set_active(lastidx) + return 0 + + + if self.getPVWastedRatio(curpe) > 0.10: + rc = self.intf.messageWindow(_("Too small"), + _("This change in the value of the " + "physical extent will waste " + "substantial space on one or more " + "of the physical volumes in the " + "volume group."), + type="custom", custom_icon="error", + custom_buttons=["gtk-cancel", _("C_ontinue")]) + if not rc: + widget.set_active(lastidx) + return 0 + + # now see if we need to fixup effect PV and LV sizes based on PE + if curval > lastval: + rc = self.reclampLV(curpe) + if not rc: + widget.set_active(lastidx) + return 0 + else: + self.updateLogVolStore() + else: + maxlv = lvm.getMaxLVSize() + for lv in self.lvs.values(): + if lv['size'] > maxlv: + self.intf.messageWindow(_("Not enough space"), + _("The physical extent size " + "cannot be changed because the " + "resulting maximum logical " + "volume size (%10.2f MB) is " + "smaller " + "than one or more of the " + "currently defined logical " + "volumes.") % (maxlv,), + custom_icon="error") + widget.set_active(lastidx) + return 0 + + widget.set_data("lastpe", curval) + widget.set_data("lastidx", widget.get_active()) + + # now actually set the VG's extent size + self.peSize = curpe + self.updateAllowedLvmPartitionsList() + self.updateVGSpaceLabels() + + def prettyFormatPESize(self, val): + """ Pretty print for PE size in KB """ + if val < 1024: + return "%s KB" % (val,) + elif val < 1024*1024: + return "%s MB" % (val/1024,) + else: + return "%s GB" % (val/1024/1024,) + + def createPEOptionMenu(self, default=4096): + peCombo = datacombo.DataComboBox() + + actualPE = [] + for curpe in lvm.getPossiblePhysicalExtents(floor=1024): + # don't show PE over 128M, unless it's the default + if curpe > 131072 and curpe != default: + continue + + actualPE.append(curpe) + val = self.prettyFormatPESize(curpe) + + peCombo.append(val, curpe) + + # First try to set the combo's active value to the default we're + # passed. If that doesn't work, just set it to the first one to + # prevent TypeErrors everywhere. + try: + peCombo.set_active(actualPE.index(default)) + except ValueError: + peCombo.set_active(0) + + peCombo.set_data("lastidx", peCombo.get_active()) + peCombo.connect("changed", self.peChangeCB) + peCombo.set_data("lastpe", default) + + return peCombo + + def clickCB(self, row, data): + model = self.lvmlist.get_model() + pvlist = self.getSelectedPhysicalVolumes() + + # get the selected row + iter = model.get_iter((string.atoi(data),)) + + # we invert val because we get called before checklist + # changes the toggle state + val = not model.get_value(iter, 0) + partname = model.get_value(iter, 1) + pv = self.storage.devicetree.getDeviceByName(partname) + if val: + self.pvs.append(pv) + else: + self.pvs.remove(pv) + try: + vg = self.getTempVG() + except DeviceError as e: + self.intf.messageWindow(_("Not enough space"), + _("You cannot remove this physical " + "volume because otherwise the " + "volume group will be too small to " + "hold the currently defined logical " + "volumes."), custom_icon="error") + self.pvs.append(pv) + return False + + self.updateVGSpaceLabels() + return True + + def createAllowedLvmPartitionsList(self): + store = gtk.TreeStore(gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + partlist = WideCheckList(2, store, self.clickCB) + + sw = gtk.ScrolledWindow() + sw.add(partlist) + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + sw.set_shadow_type(gtk.SHADOW_IN) + + origpvs = self.pvs[:] + for device in self.availlvmparts: + # clip size to current PE + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(device.size, pesize) + size_string = "%10.2f MB" % size + include = True + selected = False + + # now see if the pv is in use either by a vg in the tree or by + # the vg we are editing now + if device in origpvs: + selected = True + include = True + else: + for vg in self.storage.vgs: + if vg.name == self.vg.name: + continue + + if device in vg.pvs: + include = False + break + + if include and not origpvs: + selected = True + + if include: + partlist.append_row((device.name, size_string), selected) + if selected and device not in self.pvs: + self.pvs.append(device) + + return (partlist, sw) + + def updateAllowedLvmPartitionsList(self): + """ update sizes in pv list """ + row = 0 + for part in self.availlvmparts: + size = part.size + + # clip size to current PE + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(size, pesize) + partsize = "%10.2f MB" % size + + iter = self.lvmlist.store.get_iter((int(row),)) + self.lvmlist.store.set_value(iter, 2, partsize) + row = row + 1 + + def getCurrentLogicalVolume(self): + selection = self.logvollist.get_selection() + (model, iter) = selection.get_selected() + return iter + + def editLogicalVolume(self, lv, isNew = 0): + # Mixing logical code and gtk code is confusing to me. So I am going + # to do the logic first and then create all the gtk crap! + # + # lv -- whatever self.logvolstore.get_value returns + + #newfstypelabel = None # File system type label & combo + #newfstypeCombo = None + newfslabellabel = None # File system Label label & combo + newfslableCombo = None + #lvnamelabel = None # Logical Volume name label & entry + #lvnameentry = None + #lvsizelabel = None # Logical Volume size label & entry + #lvsizeentry = None + maxsizelabel = None # Maximum size label + #mountCombo = None # Mount Point Combo Box + #tstr = None # String that appears on top of the window + tempvg = self.getTempVG() # copy of self.vg + templv = None + cpefsos = None # lambda function that represents + # createPreExistFSOptionSection + + # Define the string + if isNew: + tstr = _("Make Logical Volume") + else: + tstr = _("Edit Logical Volume: %s") % lv['name'] + + # Create the mountCombo. This is the box where the mountpoint will + # appear. Note that if the format is swap or Raiddevice, the mount + # point is none-sense. + templuks = None + templv = self.getLVByName(lv['name'], vg=tempvg) + usedev = templv + if templv.format.type == "luks": + templuks = LUKSDevice("luks-%s" % lv['name'], + parents=[templv], + format=self.luks[lv['name']], + exists=templv.format.exists) + usedev = templuks + + if lv['format'].type == "luks": + format = self.luks[lv['name']] + else: + format = lv['format'] + + if lv['exists']: + _origlv = self.getLVByName(lv['name']) + originalFormat = _origlv.originalFormat + if originalFormat.type == "luks": + try: + _origluks = self.storage.devicetree.getChildren(_origlv)[0] + except IndexError: + pass + else: + originalFormat = _origluks.originalFormat + + mountCombo = createMountPointCombo(usedev, excludeMountPoints=["/boot"]) + + + # Stuff appears differently when the lv exists and when the lv is new. + # here we make that difference. Except for newfslabelCombo, and + # maxsizelabel all vars will have a value != None. + if not lv['exists']: + # File system type lables & combo + newfstypelabel = createAlignedLabel(_("_File System Type:")) + newfstypeCombo = createFSTypeMenu(format, fstypechangeCB,mountCombo, + ignorefs = ["mdmember", "lvmpv", "efi", "prepboot", "appleboot"]) + newfstypelabel.set_mnemonic_widget(newfstypeCombo) + + # Logical Volume name label & entry + lvnamelabel = createAlignedLabel(_("_Logical Volume Name:")) + lvnameentry = gtk.Entry(32) + lvnamelabel.set_mnemonic_widget(lvnameentry) + if lv['name']: + lvnameentry.set_text(lv['name']) + else: + lvnameentry.set_text(self.storage.createSuggestedLVName(self.getTempVG())) + + # Logical Volume size label & entry + lvsizelabel = createAlignedLabel(_("_Size (MB):")) + lvsizeentry = gtk.Entry(16) + lvsizelabel.set_mnemonic_widget(lvsizeentry) + lvsizeentry.set_text("%Ld" % lv['size']) + + # Maximum size label + max_grow = tempvg.freeSpace / lv['stripes'] + maxsizelabel = createAlignedLabel(_("(Max size is %s MB)") % + min(lvm.getMaxLVSize(), + lv['size'] + max_grow)) + + # Encrypt Check Box button. + self.lukscb = gtk.CheckButton(_("_Encrypt")) + self.lukscb.set_data("formatstate", 1) + if lv['format'].type == "luks": + self.lukscb.set_active(1) + else: + self.lukscb.set_active(0) + + else: + # File system type lable & combo + newfstypelabel = createAlignedLabel(_("Original File System Type:")) + newfstypeCombo = gtk.Label(originalFormat.name) + + # File system label label & combo + if getattr(originalFormat, "label", None): + newfslabellabel = createAlignedLabel(_("Original File System " + "Label:")) + newfslableCombo = gtk.Label(originalFormat.label) + + # Logical Volume name label & entry + lvnamelabel = createAlignedLabel(_("Logical Volume Name:")) + lvnameentry = gtk.Label(lv['name']) + + # Logical Volume size label & entry + lvsizelabel = createAlignedLabel(_("Size (MB):")) + lvsizeentry = gtk.Label(str(lv['size'])) + + # Create the File System Format Section + self.fsoptionsDict = {} + # We are going to lambda the createPreExistFSOptionSection so we can call + # it latter with two arguments, row and mainttable. + cpefsos = lambda table, row: createPreExistFSOptionSection(templv, + maintable, row, mountCombo, self.storage, + ignorefs = ["software RAID", "physical volume (LVM)", "vfat"], + luksdev=templuks) + + + # Here is where the gtk crap begins. + dialog = gtk.Dialog(tstr, self.parent) + gui.addFrame(dialog) + dialog.add_button('gtk-cancel', 2) + dialog.add_button('gtk-ok', 1) + dialog.set_position(gtk.WIN_POS_CENTER) + + # Initialize main table + maintable = gtk.Table() + maintable.set_row_spacings(5) + maintable.set_col_spacings(5) + row = 0 + + # Add the mountCombo that we previously created + lbl = createAlignedLabel(_("_Mount Point:")) + maintable.attach(lbl, 0, 1, row,row+1) + lbl.set_mnemonic_widget(mountCombo) + maintable.attach(mountCombo, 1, 2, row, row + 1) + row += 1 + + # Add the filesystem combo labels. + maintable.attach(newfstypelabel, 0, 1, row, row + 1) + maintable.attach(newfstypeCombo, 1, 2, row, row + 1) + row += 1 + + # If there is a File system lable, add it. + if newfslabellabel is not None and newfslableCombo is not None: + maintable.attach(newfslabellabel, 0, 1, row, row + 1) + maintable.attach(newfslableCombo, 1, 2, row, row + 1) + row += 1 + + # Add the logical volume name + maintable.attach(lvnamelabel, 0, 1, row, row + 1) + maintable.attach(lvnameentry, 1, 2, row, row + 1) + row += 1 + + # Add the logical volume size + maintable.attach(lvsizelabel, 0, 1, row, row + 1) + maintable.attach(lvsizeentry, 1, 2, row, row + 1) + row += 1 + + # If there is a maxsize, add it. + if maxsizelabel is not None: + maintable.attach(maxsizelabel, 1, 2, row, row + 1) + + # If we have the createPreExistFSOptionSection lamda function it means + # that we have a preexisting lv and we must call the lambda function + # to create the Pre exsisting FS option section. + if cpefsos is not None: + (row, self.fsoptionsDict) = cpefsos(maintable, row) + + # checkbutton for encryption using dm-crypt/LUKS + # FIXME: Here we could not decouple the gtk stuff from the logic because + # of the createPreExistFSOptionSection function call. We must + # decouple that function. + if not lv['exists']: + maintable.attach(self.lukscb, 0, 2, row, row + 1) + row = row + 1 + else: + self.lukscb = self.fsoptionsDict.get("lukscb") + + dialog.vbox.pack_start(maintable) + dialog.show_all() + # Here ends the gtk crap + + while 1: + rc = dialog.run() + if rc in [2, gtk.RESPONSE_DELETE_EVENT]: + if isNew: + del self.lvs[lv['name']] + dialog.destroy() + return + + actions = [] + targetSize = None + migrate = None + format = None + newluks = None + + if templv.format.type == "luks": + format = self.luks[lv['name']] + else: + format = templv.format + + if not templv.exists: + fmt_class = newfstypeCombo.get_active_value() + else: + # existing lv + fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value() + + mountpoint = mountCombo.get_children()[0].get_text().strip() + if mountpoint == _("<Not Applicable>"): + mountpoint = "" + + # validate logical volume name + lvname = lvnameentry.get_text().strip() + if not templv.exists: + err = sanityCheckLogicalVolumeName(lvname) + if err: + self.intf.messageWindow(_("Illegal Logical Volume Name"), + err, custom_icon="error") + continue + + # check that the name is not already in use + used = 0 + for _lv in self.lvs.values(): + if _lv == lv: + continue + + if _lv['name'] == lvname: + used = 1 + break + + if used: + self.intf.messageWindow(_("Illegal logical volume name"), + _("The logical volume name \"%s\" is " + "already in use. Please pick " + "another.") % (lvname,), custom_icon="error") + continue + + # test mount point + # check in pending logical volume requests + # these may not have been put in master list of requests + # yet if we have not hit 'OK' for the volume group creation + if fmt_class().mountable and mountpoint: + used = False + curmntpt = getattr(format, "mountpoint", None) + + for _lv in self.lvs.values(): + if _lv['format'].type == "luks": + _format = self.luks[_lv['name']] + else: + _format = _lv['format'] + + if not _format.mountable or curmntpt and \ + _format.mountpoint == curmntpt: + continue + + if _format.mountpoint == mountpoint: + used = True + break + + if not used: + # we checked this VG's LVs above; now check the rest of + # the devices in the tree + mountdevs = self.lvs.values() + full_name = "%s-%s" % (self.vg.name, lv['name']) + for (mp,d) in self.storage.mountpoints.iteritems(): + if (d.type != "lvmlv" or d.vg.id != self.vg.id) and \ + mp == mountpoint and \ + not (isinstance(d, LUKSDevice) and + full_name in [dev.name for dev in d.parents]): + used = True + break + + if used: + self.intf.messageWindow(_("Mount point in use"), + _("The mount point \"%s\" is in " + "use. Please pick another.") % + (mountpoint,), + custom_icon="error") + continue + + # check that size specification is numeric and positive + if not templv.exists: + badsize = 0 + try: + size = long(lvsizeentry.get_text()) + except: + badsize = 1 + + if badsize or size <= 0: + self.intf.messageWindow(_("Illegal size"), + _("The requested size as entered is " + "not a valid number greater " + "than 0."), custom_icon="error") + continue + else: + size = templv.size + + # check that size specification is within limits + pesize = int(self.peCombo.get_active_value()) / 1024.0 + size = lvm.clampSize(size, pesize, roundup=True) + maxlv = lvm.getMaxLVSize() + if size > maxlv: + self.intf.messageWindow(_("Not enough space"), + _("The current requested size " + "(%(size)10.2f MB) is larger than " + "the maximum logical volume size " + "(%(maxlv)10.2f MB). " + "To increase this limit you can " + "create more Physical Volumes from " + "unpartitioned disk space and " + "add them to this Volume Group.") + % {'size': size, 'maxlv': maxlv}, + custom_icon="error") + continue + + # Ok -- now we've done all the checks to validate the + # user-specified parameters. Time to set up the device... + origname = templv.lvname + if not templv.exists: + templv._name = lvname + try: + templv.size = size + except ValueError: + self.intf.messageWindow(_("Not enough space"), + _("The logical volumes you have " + "configured require %(size)d MB," + " but the volume group only has " + "%(tempvgsize)d MB. Please " + "either make the volume group " + "larger or make the logical " + "volume(s) smaller.") + % {'size': size, + 'tempvgsize': tempvg.size}, + custom_icon="error") + continue + + format = fmt_class(mountpoint=mountpoint) + if self.lukscb and self.lukscb.get_active(): + if templv.format.type != "luks": + newluks = format + format = getFormat("luks", + passphrase=self.storage.encryptionPassphrase) + else: + newluks = format + format = templv.format + + templv.format = format + else: + # existing lv + if self.fsoptionsDict.has_key("formatcb") and \ + self.fsoptionsDict["formatcb"].get_active(): + format = fmt_class(mountpoint=mountpoint) + if self.lukscb and self.lukscb.get_active() and \ + templv.format.type != "luks": + newluks = format + format = getFormat("luks", + device=templv.path, + passphrase=self.storage.encryptionPassphrase) + elif self.lukscb and self.lukscb.get_active(): + newluks = format + format = templv.format + + templv.format = format + elif self.fsoptionsDict.has_key("formatcb") and \ + not self.fsoptionsDict["formatcb"].get_active(): + templv.format = templv.originalFormat + format = templv.format + + if format.mountable: + format.mountpoint = mountpoint + + if self.fsoptionsDict.has_key("migratecb") and \ + self.fsoptionsDict["migratecb"].get_active(): + format.migrate = True + + if self.fsoptionsDict.has_key("resizecb") and self.fsoptionsDict["resizecb"].get_active(): + targetSize = self.fsoptionsDict["resizesb"].get_value_as_int() + templv.targetSize = targetSize + + if format.exists and format.mountable and format.mountpoint: + tempdev = StorageDevice('tmp', format=format) + if self.storage.formatByDefault(tempdev) and \ + not queryNoFormatPreExisting(self.intf): + continue + + # everything ok + break + + if templv.format.type == "luks": + if newluks: + self.luks[templv.lvname] = newluks + + if self.luks.has_key(origname) and origname != templv.lvname: + self.luks[templv.lvname] = self.luks[origname] + del self.luks[templv.lvname] + elif templv.format.type != "luks" and self.luks.has_key(origname): + del self.luks[origname] + + self.lvs[templv.lvname] = {'name': templv.lvname, + 'size': templv.size, + 'format': templv.format, + 'originalFormat': templv.originalFormat, + 'stripes': templv.stripes, + 'logSize': templv.logSize, + 'snapshotSpace': templv.snapshotSpace, + 'exists': templv.exists} + if self.lvs.has_key(origname) and origname != templv.lvname: + del self.lvs[origname] + + self.updateLogVolStore() + self.updateVGSpaceLabels() + dialog.destroy() + return + + def editCurrentLogicalVolume(self): + iter = self.getCurrentLogicalVolume() + + if iter is None: + return + + logvolname = self.logvolstore.get_value(iter, 0) + lv = self.lvs[logvolname] + self.editLogicalVolume(lv) + + def addLogicalVolumeCB(self, widget): + if self.numAvailableLVSlots() < 1: + self.intf.messageWindow(_("No free slots"), + P_("You cannot create more than %d logical volume " + "per volume group.", + "You cannot create more than %d logical volumes " + "per volume group.", lvm.MAX_LV_SLOTS) + % (lvm.MAX_LV_SLOTS,), + custom_icon="error") + return + + (total, used, free) = self.computeSpaceValues() + if free <= 0: + self.intf.messageWindow(_("No free space"), + _("There is no room left in the " + "volume group to create new logical " + "volumes. " + "To add a logical volume you must " + "reduce the size of one or more of " + "the currently existing " + "logical volumes"), custom_icon="error") + return + + tempvg = self.getTempVG() + name = self.storage.createSuggestedLVName(tempvg) + format = getFormat(self.storage.defaultFSType) + self.lvs[name] = {'name': name, + 'size': free, + 'format': format, + 'originalFormat': format, + 'stripes': 1, + 'logSize': 0, + 'snapshotSpace': 0, + 'exists': False} + self.editLogicalVolume(self.lvs[name], isNew = 1) + return + + def editLogicalVolumeCB(self, widget): + self.editCurrentLogicalVolume() + return + + def delLogicalVolumeCB(self, widget): + iter = self.getCurrentLogicalVolume() + if iter is None: + return + + logvolname = self.logvolstore.get_value(iter, 0) + if logvolname is None: + return + + rc = self.intf.messageWindow(_("Confirm Delete"), + _("Are you sure you want to delete the " + "logical volume \"%s\"?") % (logvolname,), + type = "custom", custom_buttons=["gtk-cancel", _("_Delete")], custom_icon="warning") + if not rc: + return + + del self.lvs[logvolname] + self.logvolstore.remove(iter) + self.updateVGSpaceLabels() + return + + def logvolActivateCb(self, view, path, col): + self.editCurrentLogicalVolume() + + def getSelectedPhysicalVolumes(self): + model = self.lvmlist.get_model() + pv = [] + next = model.get_iter_first() + currow = 0 + while next is not None: + iter = next + val = model.get_value(iter, 0) + partname = model.get_value(iter, 1) + + if val: + dev = self.storage.devicetree.getDeviceByName(partname) + pv.append(dev) + + next = model.iter_next(iter) + currow = currow + 1 + + return pv + + def computeVGSize(self, pvlist, curpe): + availSpaceMB = 0L + for pv in pvlist: + # have to clamp pvsize to multiple of PE + # XXX why the subtraction? fudging metadata? + pvsize = lvm.clampSize(pv.size, curpe) - (curpe/1024) + + availSpaceMB = availSpaceMB + pvsize + + log.info("computeVGSize: vgsize is %s" % (availSpaceMB,)) + return availSpaceMB + + def updateLogVolStore(self): + self.logvolstore.clear() + for lv in self.lvs.values(): + iter = self.logvolstore.append() + if lv['format'].type == "luks": + format = self.luks[lv['name']] + else: + format = lv['format'] + + mntpt = getattr(format, "mountpoint", "") + if lv['name']: + self.logvolstore.set_value(iter, 0, lv['name']) + + if format.type and format.mountable: + self.logvolstore.set_value(iter, 1, mntpt) + else: + self.logvolstore.set_value(iter, 1, "N/A") + + self.logvolstore.set_value(iter, 2, "%Ld" % lv['size']) + + def updateVGSpaceLabels(self): + (total, used, free) = self.computeSpaceValues() + + self.totalSpaceLabel.set_text("%10.2f MB" % (total,)) + self.usedSpaceLabel.set_text("%10.2f MB" % (used,)) + + if total > 0: + usedpercent = (100.0*used)/total + else: + usedpercent = 0.0 + + self.usedPercentLabel.set_text("(%4.1f %%)" % (usedpercent,)) + + self.freeSpaceLabel.set_text("%10.2f MB" % (free,)) + if total > 0: + freepercent = (100.0*free)/total + else: + freepercent = 0.0 + + self.freePercentLabel.set_text("(%4.1f %%)" % (freepercent,)) + +# +# run the VG editor we created +# + def run(self): + if self.dialog is None: + return [] + + while 1: + rc = self.dialog.run() + + if rc in [2, gtk.RESPONSE_DELETE_EVENT]: + self.destroy() + return [] + + pvlist = self.getSelectedPhysicalVolumes() + + # check volume name + volname = self.volnameEntry.get_text().strip() + err = sanityCheckVolumeGroupName(volname) + if err: + self.intf.messageWindow(_("Invalid Volume Group Name"), err, + custom_icon="error") + continue + + origvname = self.vg.name + + if origvname != volname: + # maybe we should see if _any_ device has this name + if volname in [vg.name for vg in self.storage.vgs]: + self.intf.messageWindow(_("Name in use"), + _("The volume group name \"%s\" is " + "already in use. Please pick " + "another." % (volname,)), + custom_icon="error") + continue + + # get physical extent + pesize = int(self.peCombo.get_active_value()) / 1024.0 + + # everything ok + break + return self.convertToActions() + + def convertToActions(self): + # here we have to figure out what all was done and convert it to + # devices and actions + # + # set up the vg with the right pvs + # set up the lvs + # set up the lvs' formats + # + log.debug("finished editing vg") + log.debug("pvs: %s" % [p.name for p in self.pvs]) + log.debug("luks: %s" % self.luks.keys()) + volname = self.volnameEntry.get_text().strip() + pesize = int(self.peCombo.get_active_value()) / 1024.0 + for lv in self.lvs.itervalues(): + log.debug("lv %s" % lv) + _luks = self.luks.get(lv['name']) + if _luks: + log.debug(" luks: %s" % _luks) + + actions = [] + origlvs = self.vg.lvs + if not self.vg.exists: + log.debug("non-existing vg -- setting up lvs, pvs, name, pesize") + # remove all of the lvs + for lv in self.vg.lvs: + self.vg._removeLogVol(lv) + + # set up the pvs + for pv in self.vg.pvs: + if pv not in self.pvs: + self.vg._removePV(pv) + for pv in self.pvs: + if pv not in self.vg.pvs: + self.vg._addPV(pv) + + self.vg.name = volname + self.vg.peSize = pesize + + if self.isNew: + actions = [ActionCreateDevice(self.vg)] + + # Schedule destruction of all non-existing lvs, their formats, + # luks devices, &c. Also destroy devices that have been removed. + for lv in origlvs: + log.debug("old lv %s..." % lv.lvname) + if not lv.exists or lv.lvname not in self.lvs or \ + (not self.lvs[lv.lvname]['exists'] and lv.exists): + log.debug("removing lv %s" % lv.lvname) + if lv.format.type == "luks": + try: + _luksdev = self.storage.devicetree.getChildren(lv)[0] + except IndexError: + pass + else: + if _luksdev.format.type: + actions.append(ActionDestroyFormat(_luksdev)) + + actions.append(ActionDestroyDevice(_luksdev)) + + if lv.format.type: + actions.append(ActionDestroyFormat(lv)) + + if lv in self.vg.lvs: + self.vg._removeLogVol(lv) + + actions.append(ActionDestroyDevice(lv)) + + # schedule creation of all new lvs, their formats, luks devices, &c + tempvg = self.getTempVG() + for lv in tempvg.lvs: + log.debug("new lv %s" % lv) + if not lv.exists: + log.debug("creating lv %s" % lv.lvname) + # create the device + newlv = LVMLogicalVolumeDevice(lv.lvname, + self.vg, + size=lv.size) + actions.append(ActionCreateDevice(newlv)) + + # create the format + mountpoint = getattr(lv.format, "mountpoint", None) + format = getFormat(lv.format.type, + mountpoint=mountpoint, + device=newlv.path) + actions.append(ActionCreateFormat(newlv, format)) + + if lv.format.type == "luks": + # create the luks device + newluks = LUKSDevice("luks-%s" % newlv.name, + parents=[newlv]) + actions.append(ActionCreateDevice(newluks)) + + # create the luks format + oldfmt = self.luks[lv.lvname] + mountpoint = getattr(oldfmt, "mountpoint", None) + format = getFormat(oldfmt.type, + mountpoint=mountpoint, + device=newluks.path) + actions.append(ActionCreateFormat(newluks, format)) + else: + log.debug("lv %s already exists" % lv.lvname) + # this lv is preexisting. check for resize and reformat. + # first, get the real/original lv + origlv = self.getLVByName(lv.lvname) + if lv.resizable and lv.targetSize != origlv.size: + actions.append(ActionResizeDevice(origlv, lv.targetSize)) + + if lv.format.exists: + log.debug("format already exists") + if lv.format.type == "luks": + # see if the luks device already exists + try: + usedev = self.storage.devicetree.getChildren(origlv)[0] + except IndexError: + # the luks device does not exist, meaning we + # do not have a key for it + continue + + format = self.luks[lv.lvname] + if not format.exists: + actions.append(ActionCreateFormat(usedev, format)) + else: + usedev = origlv + format = lv.format + + # no formatting action requested, meaning we should + # cancel all format create/destroy actions + if format == usedev.originalFormat: + devicetree = self.storage.devicetree + cancel = [] + if origlv.originalFormat.type == "luks": + path = "/dev/mapper/luks-%s" % origlv.originalFormat.uuid + cancel.extend(devicetree.findActions(path=path)) + + cancel.extend(devicetree.findActions(type="create", + object="format", + devid=origlv.id)) + cancel.extend(devicetree.findActions(type="destroy", + object="format", + devid=origlv.id)) + for action in cancel: + devicetree.cancelAction(action) + + # even though we cancelled a bunch of actions, it's + # pretty much impossible to be sure we cancelled them + # in the correct order. make sure things are back to + # their original state. + if origlv.format.type == "luks": + try: + usedev = devicetree.getChildren(origlv)[0] + except IndexError: + usedev = origlv + else: + usedev.format = usedev.originalFormat + else: + usedev = origlv + + if hasattr(format, "mountpoint"): + usedev.format.mountpoint = format.mountpoint + + if format.migratable and format.migrate and \ + not usedev.format.migrate: + usedev.format.migrate = format.migrate + actions.append(ActionMigrateFormat(usedev)) + + # check the lv's format also, explicitly, in case it is + # encrypted. in this case we must check them both. + if format.resizable and lv.format.resizable and \ + lv.targetSize != format.currentSize and \ + usedev.format.exists: + new_size = lv.targetSize + actions.append(ActionResizeFormat(usedev, new_size)) + elif lv.format.type: + log.debug("new format: %s" % lv.format.type) + # destroy old format and any associated luks devices + if origlv.format.type: + if origlv.format.type == "luks": + # destroy the luks device and its format + try: + _luksdev = self.storage.devicetree.getChildren(origlv)[0] + except IndexError: + pass + else: + if _luksdev.format.type: + # this is probably unnecessary + actions.append(ActionDestroyFormat(_luksdev)) + + actions.append(ActionDestroyDevice(_luksdev)) + + actions.append(ActionDestroyFormat(origlv)) + + # create the format + mountpoint = getattr(lv.format, "mountpoint", None) + format = getFormat(lv.format.type, + mountpoint=mountpoint, + device=origlv.path) + actions.append(ActionCreateFormat(origlv, format)) + + if lv.format.type == "luks": + # create the luks device + newluks = LUKSDevice("luks-%s" % origlv.name, + parents=[origlv]) + actions.append(ActionCreateDevice(newluks)) + + # create the luks format + tmpfmt = self.luks[lv.lvname] + mountpoint = getattr(tmpfmt, "mountpoint", None) + format = getFormat(tmpfmt.type, + mountpoint=mountpoint, + device=newluks.path) + actions.append(ActionCreateFormat(newluks, format)) + else: + log.debug("no format!?") + + return actions + + def destroy(self): + if self.dialog: + self.dialog.destroy() + self.dialog = None + + def getLVByName(self, name, vg=None): + if vg is None: + vg = self.vg + + for lv in vg.lvs: + if lv.lvname == name or lv.name == name: + return lv + + def __init__(self, anaconda, intf, parent, vg, isNew = 0): + self.storage = anaconda.storage + + # the vg instance we were passed + self.vg = vg + self.peSize = vg.peSize + self.pvs = self.vg.pvs[:] + + # a dict of dicts + # keys are lv names + # values are dicts representing the lvs + # name, size, format instance, exists + self.lvs = {} + + # a dict of luks devices + # keys are lv names + # values are formats of the mapped devices + self.luks = {} + + self.isNew = isNew + self.intf = intf + self.parent = parent + self.actions = [] + + for lv in self.vg.lvs: + self.lvs[lv.lvname] = {"name": lv.lvname, + "size": lv.size, + "format": copy.copy(lv.format), + "originalFormat": lv.originalFormat, + "stripes": lv.stripes, + "logSize": lv.logSize, + "snapshotSpace": lv.snapshotSpace, + "exists": lv.exists} + + if lv.format.type == "luks": + try: + self.luks[lv.lvname] = self.storage.devicetree.getChildren(lv)[0].format + except IndexError: + self.luks[lv.lvname] = lv.format + + self.availlvmparts = self.storage.unusedPVs(vg=vg) + + # if no PV exist, raise an error message and return + if len(self.availlvmparts) < 1: + self.intf.messageWindow(_("Not enough physical volumes"), + _("At least one unused physical " + "volume partition is " + "needed to create an LVM Volume Group.\n\n" + "Create a partition or RAID array " + "of type \"physical volume (LVM)\" and then " + "select the \"LVM\" option again."), + custom_icon="error") + self.dialog = None + return + + if isNew: + tstr = _("Make LVM Volume Group") + else: + try: + tstr = _("Edit LVM Volume Group: %s") % (vg.name,) + except AttributeError: + tstr = _("Edit LVM Volume Group") + + dialog = gtk.Dialog(tstr, self.parent) + gui.addFrame(dialog) + dialog.add_button('gtk-cancel', 2) + dialog.add_button('gtk-ok', 1) + + dialog.set_position(gtk.WIN_POS_CENTER) + + maintable = gtk.Table() + maintable.set_row_spacings(5) + maintable.set_col_spacings(5) + row = 0 + + # volume group name + if not vg.exists: + lbl = createAlignedLabel(_("_Volume Group Name:")) + self.volnameEntry = gtk.Entry(16) + lbl.set_mnemonic_widget(self.volnameEntry) + if not self.isNew: + self.volnameEntry.set_text(self.vg.name) + else: + self.volnameEntry.set_text(self.storage.createSuggestedVGName(anaconda.network)) + else: + lbl = createAlignedLabel(_("Volume Group Name:")) + self.volnameEntry = gtk.Label(self.vg.name) + + maintable.attach(lbl, 0, 1, row, row + 1, + gtk.EXPAND|gtk.FILL, gtk.SHRINK) + maintable.attach(self.volnameEntry, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + row = row + 1 + + lbl = createAlignedLabel(_("_Physical Extent:")) + self.peCombo = self.createPEOptionMenu(self.vg.peSize * 1024) + lbl.set_mnemonic_widget(self.peCombo) + if vg.exists: + self.peCombo.set_sensitive(False) + + maintable.attach(lbl, 0, 1, row, row + 1, + gtk.EXPAND|gtk.FILL, gtk.SHRINK) + maintable.attach(self.peCombo, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + row = row + 1 + + (self.lvmlist, sw) = self.createAllowedLvmPartitionsList() + if vg.exists: + self.lvmlist.set_sensitive(False) + self.lvmlist.set_size_request(275, 80) + lbl = createAlignedLabel(_("Physical Volumes to _Use:")) + lbl.set_mnemonic_widget(self.lvmlist) + maintable.attach(lbl, 0, 1, row, row + 1) + maintable.attach(sw, 1, 2, row, row + 1) + row = row + 1 + + maintable.attach(createAlignedLabel(_("Used Space:")), 0, 1, row, + row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + lbox = gtk.HBox() + self.usedSpaceLabel = gtk.Label("") + labelalign = gtk.Alignment() + labelalign.set(1.0, 0.5, 0.0, 0.0) + labelalign.add(self.usedSpaceLabel) + lbox.pack_start(labelalign, False, False) + self.usedPercentLabel = gtk.Label("") + labelalign = gtk.Alignment() + labelalign.set(1.0, 0.5, 0.0, 0.0) + labelalign.add(self.usedPercentLabel) + lbox.pack_start(labelalign, False, False, padding=10) + maintable.attach(lbox, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + maintable.set_row_spacing(row, 0) + row = row + 1 + + maintable.attach(createAlignedLabel(_("Free Space:")), 0, 1, row, + row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + lbox = gtk.HBox() + self.freeSpaceLabel = gtk.Label("") + labelalign = gtk.Alignment() + labelalign.set(1.0, 0.5, 0.0, 0.0) + labelalign.add(self.freeSpaceLabel) + lbox.pack_start(labelalign, False, False) + self.freePercentLabel = gtk.Label("") + labelalign = gtk.Alignment() + labelalign.set(1.0, 0.5, 0.0, 0.0) + labelalign.add(self.freePercentLabel) + lbox.pack_start(labelalign, False, False, padding=10) + + maintable.attach(lbox, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + maintable.set_row_spacing(row, 0) + row = row + 1 + + maintable.attach(createAlignedLabel(_("Total Space:")), 0, 1, row, + row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + self.totalSpaceLabel = gtk.Label("") + labelalign = gtk.Alignment() + labelalign.set(0.0, 0.5, 0.0, 0.0) + labelalign.add(self.totalSpaceLabel) + maintable.attach(labelalign, 1, 2, row, row + 1, gtk.EXPAND|gtk.FILL, gtk.SHRINK) + maintable.set_row_spacing(row, 5) + row = row + 1 + + # populate list of logical volumes + lvtable = gtk.Table() + lvtable.set_row_spacings(5) + lvtable.set_col_spacings(5) + self.logvolstore = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + + if self.vg.lvs: + for lv in self.vg.lvs: + iter = self.logvolstore.append() + self.logvolstore.set_value(iter, 0, lv.lvname) + if lv.format.type == "luks": + try: + format = self.storage.devicetree.getChildren(lv)[0].format + except IndexError: + format = lv.format + else: + format = lv.format + + if getattr(format, "mountpoint", None): + self.logvolstore.set_value(iter, 1, + format.mountpoint) + else: + self.logvolstore.set_value(iter, 1, "") + self.logvolstore.set_value(iter, 2, "%Ld" % lv.size) + + self.logvollist = gtk.TreeView(self.logvolstore) + col = gtk.TreeViewColumn(_("Logical Volume Name"), + gtk.CellRendererText(), text=0) + self.logvollist.append_column(col) + col = gtk.TreeViewColumn(_("Mount Point"), + gtk.CellRendererText(), text=1) + self.logvollist.append_column(col) + col = gtk.TreeViewColumn(_("Size (MB)"), + gtk.CellRendererText(), text=2) + self.logvollist.append_column(col) + self.logvollist.connect('row-activated', self.logvolActivateCb) + + sw = gtk.ScrolledWindow() + sw.add(self.logvollist) + sw.set_size_request(100, 100) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.set_shadow_type(gtk.SHADOW_IN) + lvtable.attach(sw, 0, 1, 0, 1) + + # button box of options + lvbbox = gtk.VBox() + add = gtk.Button(_("_Add")) + add.connect("clicked", self.addLogicalVolumeCB) + lvbbox.pack_start(add) + edit = gtk.Button(_("_Edit")) + edit.connect("clicked", self.editLogicalVolumeCB) + lvbbox.pack_start(edit) + delete = gtk.Button(_("_Delete")) + delete.connect("clicked", self.delLogicalVolumeCB) + lvbbox.pack_start(delete) + + lvalign = gtk.Alignment() + lvalign.set(0.5, 0.0, 0.0, 0.0) + lvalign.add(lvbbox) + lvtable.attach(lvalign, 1, 2, 0, 1, gtk.SHRINK, gtk.SHRINK) + + # pack all logical volumne stuff in a frame + lvtable.set_border_width(12) + l = gtk.Label() + l.set_markup_with_mnemonic("<b>%s</b>" %(_("_Logical Volumes"),)) + l.set_mnemonic_widget(self.logvollist) + frame = gtk.Frame() + frame.set_label_widget(l) + frame.add(lvtable) + frame.set_shadow_type(gtk.SHADOW_NONE) + +# dialog.vbox.pack_start(frame) + maintable.attach(frame, 0, 2, row, row+1) + row = row + 1 + + dialog.vbox.pack_start(maintable) + dialog.set_size_request(550, 450) + dialog.show_all() + + # set space labels to correct values + self.updateVGSpaceLabels() + + self.dialog = dialog |