#
# raid_dialog_gui.py: dialog for editting a raid request
#
# Copyright (C) 2001, 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 .
#
# Author(s): Michael Fulbright
# Jeremy Katz
#
import copy
import gobject
import gtk
import datacombo
import gui
import storage.devicelibs.mdraid as mdraidlib
from storage.devices import *
from storage.deviceaction import *
from partition_ui_helpers_gui import *
from constants import *
from partIntfHelpers import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
class RaidEditor:
def _adjust_spares_button(self, raidlevel, selected_count):
maxspares = 0
if raidlevel is not None:
maxspares = mdraidlib.get_raid_max_spares(raidlevel, selected_count)
if maxspares > 0:
adj = self.sparesb.get_adjustment()
value = int(min(adj.value, maxspares))
self.sparesb.set_sensitive(1)
adj.configure(value=value, lower=0, upper=maxspares, step_increment=1,
page_increment=0, page_size=0)
else:
self.sparesb.set_value(0)
self.sparesb.set_sensitive(0)
def createAllowedRaidPartitionsList(self, allraidparts, reqraidpart,
preexist):
store = gtk.TreeStore(gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_STRING)
columns = ['Drive', 'Size']
partlist = WideCheckList(columns, store,
clickCB=self.raidlist_toggle_callback)
sw = gtk.ScrolledWindow()
sw.add(partlist)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.set_shadow_type(gtk.SHADOW_IN)
tempDevList = []
if not self.isNew:
# We need this list if we are editing.
for dev in reqraidpart:
tempDevList.append(dev)
partrow = 0
for part in allraidparts:
partname = "%s" % part.name
partsize = "%8.0f MB" % part.size
if part in tempDevList:
#list the partition and put it as selected
partlist.append_row((partname, partsize), True)
else:
if not self.origrequest.exists:
partlist.append_row((partname, partsize), False)
return (partlist, sw)
def createRaidLevelMenu(self, levels, reqlevel):
levelcombo = gtk.combo_box_new_text()
defindex = 0
if mdraidlib.RAID1 in levels:
defindex = levels.index(mdraidlib.RAID1)
i = 0
for lev in levels:
levelcombo.append_text("RAID%d" % lev)
if reqlevel is not None and lev == reqlevel:
defindex = i
i = i + 1
levelcombo.set_active(defindex)
levelcombo.connect("changed", self.raidlevelchangeCB)
return levelcombo
def createRaidMinorMenu(self, minors, reqminor):
minorcombo = datacombo.DataComboBox()
defindex = 0
i = 0
for minor in minors:
name = "md%d" % minor
if name in self.storage.devicetree._ignoredDisks:
continue
minorcombo.append(name, minor)
if reqminor and minor == reqminor:
defindex = i
i = i + 1
minorcombo.set_active(defindex)
return minorcombo
def raidlevelchangeCB(self, widget):
raidlevel = widget.get_model()[widget.get_active()][0]
selected_count = self._total_selected_members()
self._adjust_spares_button(raidlevel, selected_count)
def run(self):
if self.dialog is None:
return []
while 1:
self.allow_ok_button(self._total_selected_members())
rc = self.dialog.run()
# user hit cancel, do nothing
if rc in [2, gtk.RESPONSE_DELETE_EVENT]:
self.destroy()
return []
actions = []
luksdev = None
raidmembers = []
migrate = None
model = self.raidlist.get_model()
iter = model.get_iter_first()
format = None
while iter:
val = model.get_value(iter, 0)
part = model.get_value(iter, 1)
if val:
dev = self.storage.devicetree.getDeviceByName(part)
raidmembers.append(dev)
iter = model.iter_next(iter)
# The user has to select some devices to be part of the array.
if not raidmembers:
continue
mountpoint = self.mountCombo.get_children()[0].get_text()
(sensitive,) = self.mountCombo.get_properties('sensitive')
if sensitive and mountpoint:
msg = sanityCheckMountPoint(mountpoint)
if msg:
self.intf.messageWindow(_("Mount Point Error"),
msg,
custom_icon="error")
self.dialog.present()
continue
used = False
for (mp, dev) in self.storage.mountpoints.iteritems():
if mp == mountpoint and \
dev.id != self.origrequest.id and \
not (self.origrequest.format.type == "luks" and
self.origrequest in dev.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")
self.dialog.present()
continue
if not self.origrequest.exists:
# new device
fmt_class = self.fstypeCombo.get_active_value()
raidminor = int(self.minorCombo.get_active_value())
model = self.levelcombo.get_model()
raidlevel = model[self.levelcombo.get_active()][0]
if not mdraidlib.isRaid(mdraidlib.RAID0, raidlevel):
self.sparesb.update()
spares = self.sparesb.get_value_as_int()
else:
spares = 0
format = fmt_class(mountpoint=mountpoint)
members = len(raidmembers) - spares
try:
request = self.storage.newMDArray(minor=raidminor,
level=raidlevel,
format=format,
parents=raidmembers,
totalDevices=len(raidmembers),
memberDevices=members)
except ValueError, e:
self.intf.messageWindow(_("Error"), str(e),
custom_icon="error")
self.dialog.present()
continue
# we must destroy luks leaf before original raid request
if self.origrequest.format.type == "luks":
# => not self.isNew
# destroy luks format and mapped device
# XXX remove catching, it should always succeed
try:
luksdev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
pass
else:
actions.append(ActionDestroyFormat(luksdev))
actions.append(ActionDestroyDevice(luksdev))
luksdev = None
if self.lukscb and self.lukscb.get_active():
luksdev = LUKSDevice("luks-%s" % request.name,
format=format,
parents=request)
format = getFormat("luks",
passphrase=self.storage.encryptionPassphrase)
request.format = format
elif self.lukscb and not self.lukscb.get_active() and \
self.origrequest.format.type == "luks":
# XXXRV not needed as we destroy origrequest ?
actions.append(ActionDestroyFormat(self.origrequest))
if not self.isNew:
# This may be handled in devicetree.registerAction,
# but not in case when we change minor and thus
# device name/path (at least with current md)
actions.append(ActionDestroyDevice(self.origrequest))
actions.append(ActionCreateDevice(request))
actions.append(ActionCreateFormat(request))
else:
# existing device
fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value()
if self.fsoptionsDict.has_key("formatcb") and \
self.fsoptionsDict["formatcb"].get_active():
format = fmt_class(mountpoint=mountpoint)
if self.fsoptionsDict.has_key("lukscb") and \
self.fsoptionsDict["lukscb"].get_active() and \
(self.origrequest.format.type != "luks" or
(self.origrequest.format.exists and
not self.origrequest.format.hasKey)):
luksdev = LUKSDevice("luks-%s" % self.origrequest.name,
format=format,
parents=self.origrequest)
format = getFormat("luks",
device=self.origrequest.path,
passphrase=self.storage.encryptionPassphrase)
elif self.fsoptionsDict.has_key("lukscb") and \
not self.fsoptionsDict["lukscb"].get_active() and \
self.origrequest.format.type == "luks":
# destroy luks format and mapped device
try:
luksdev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
pass
else:
actions.append(ActionDestroyFormat(luksdev))
actions.append(ActionDestroyDevice(luksdev))
luksdev = None
actions.append(ActionDestroyFormat(self.origrequest))
elif self.fsoptionsDict.has_key("formatcb") and \
not self.fsoptionsDict["formatcb"].get_active():
# if the format checkbutton is inactive, cancel all
# actions on this device that create or destroy formats
devicetree = self.storage.devicetree
request = self.origrequest
cancel = []
if request.originalFormat.type == "luks":
path = "/dev/mapper/luks-%s" % request.originalFormat.uuid
cancel.extend(devicetree.findActions(path=path))
cancel.extend(devicetree.findActions(type="destroy",
object="format",
devid=request.id))
cancel.extend(devicetree.findActions(type="create",
object="format",
devid=request.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.
request.format = request.originalFormat
if request.format.type == "luks":
try:
usedev = devicetree.getChildren(request)[0]
except IndexError:
usedev = request
else:
usedev.format = usedev.originalFormat
else:
usedev = request
if usedev.format.mountable:
usedev.format.mountpoint = mountpoint
if self.origrequest.format.mountable:
self.origrequest.format.mountpoint = mountpoint
if self.fsoptionsDict.has_key("migratecb") and \
self.fsoptionsDict["migratecb"].get_active():
if self.origrequest.format.type == "luks":
try:
usedev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
usedev = self.origrequest
else:
usedev = self.origrequest
migrate = True
if self.origrequest.format.exists and not format and \
self.storage.formatByDefault(self.origrequest):
if not queryNoFormatPreExisting(self.intf):
continue
if format:
actions.append(ActionCreateFormat(self.origrequest, format))
# everything ok, break out
break
if luksdev:
actions.append(ActionCreateDevice(luksdev))
actions.append(ActionCreateFormat(luksdev))
if migrate:
actions.append(ActionMigrateFormat(usedev))
return actions
def destroy(self):
if self.dialog:
self.dialog.destroy()
self.dialog = None
def __init__(self, storage, intf, parent, origrequest, isNew = 0):
self.storage = storage
self.origrequest = origrequest
self.isNew = isNew
self.intf = intf
self.parent = parent
self.dialog = None
#
# start of editRaidRequest
#
availraidparts = self.storage.unusedMDMembers(array=self.origrequest)
# if no raid partitions exist, raise an error message and return
if len(availraidparts) < 2:
dlg = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK,
_("At least two unused software RAID "
"partitions are needed to create "
"a RAID device.\n\n"
"First create at least two partitions "
"of type \"software RAID\", and then "
"select the \"RAID\" option again."))
gui.addFrame(dlg)
dlg.show_all()
dlg.set_position(gtk.WIN_POS_CENTER)
dlg.run()
dlg.destroy()
return
if isNew:
tstr = _("Make RAID Device")
else:
if origrequest.minor is not None:
tstr = _("Edit RAID Device: %s") % (origrequest.path,)
else:
tstr = _("Edit RAID Device")
dialog = gtk.Dialog(tstr, self.parent)
gui.addFrame(dialog)
dialog.add_button('gtk-cancel', 2)
self.ok_button = 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
# we'll maybe add this further down
self.lukscb = gtk.CheckButton(_("_Encrypt"))
self.lukscb.set_data("formatstate", 1)
if origrequest.format.type == "luks":
try:
luksdev = self.storage.devicetree.getChildren(origrequest)[0]
except IndexError:
luksdev = None
usedev = origrequest
format = origrequest.format
else:
usedev = luksdev
format = usedev.format
else:
luksdev = None
usedev = origrequest
format = origrequest.format
# Mount Point entry
lbl = createAlignedLabel(_("_Mount Point:"))
maintable.attach(lbl, 0, 1, row, row + 1)
self.mountCombo = createMountPointCombo(usedev)
lbl.set_mnemonic_widget(self.mountCombo)
maintable.attach(self.mountCombo, 1, 2, row, row + 1)
row = row + 1
# Filesystem Type
if not origrequest.exists:
lbl = createAlignedLabel(_("_File System Type:"))
maintable.attach(lbl, 0, 1, row, row + 1)
self.fstypeCombo = createFSTypeMenu(format,
fstypechangeCB,
self.mountCombo,
ignorefs = ["mdmember", "efi", "prepboot", "appleboot"])
lbl.set_mnemonic_widget(self.fstypeCombo)
maintable.attach(self.fstypeCombo, 1, 2, row, row + 1)
row += 1
else:
maintable.attach(createAlignedLabel(_("Original File System Type:")),
0, 1, row, row + 1)
self.fstypeCombo = gtk.Label(usedev.originalFormat.name)
maintable.attach(self.fstypeCombo, 1, 2, row, row + 1)
row += 1
if getattr(usedev.originalFormat, "label", None):
maintable.attach(createAlignedLabel(_("Original File System "
"Label:")),
0, 1, row, row + 1)
maintable.attach(gtk.Label(usedev.originalFormat.label),
1, 2, row, row + 1)
row += 1
# raid minors
lbl = createAlignedLabel(_("RAID _Device:"))
maintable.attach(lbl, 0, 1, row, row + 1)
if not origrequest.exists:
availminors = self.storage.unusedMDMinors[:16]
reqminor = origrequest.minor
if reqminor is not None and reqminor not in availminors:
availminors.append(reqminor)
availminors.sort()
self.minorCombo = self.createRaidMinorMenu(availminors, reqminor)
lbl.set_mnemonic_widget(self.minorCombo)
else:
self.minorCombo = gtk.Label("%s" %(origrequest.name,))
maintable.attach(self.minorCombo, 1, 2, row, row + 1)
row = row + 1
# raid level
lbl = createAlignedLabel(_("RAID _Level:"))
maintable.attach(lbl, 0, 1, row, row + 1)
if not origrequest.exists:
# Create here, pack below
# create the raid level combobox:
self.levelcombo = self.createRaidLevelMenu(mdraidlib.raid_levels,
origrequest.level)
# now the number-of-spares spin button:
spareAdj = gtk.Adjustment(value=0, upper=0, step_incr=1)
self.sparesb = gtk.SpinButton(spareAdj)
# adjust the max number of spares depending on the default raid level
level_index = self.levelcombo.get_active()
selected_level = self.levelcombo.get_model()[level_index][0]
self._adjust_spares_button(selected_level, origrequest.totalDevices)
# if there's a specific spares number request, set it
self.sparesb.set_value(origrequest.spares)
lbl.set_mnemonic_widget(self.levelcombo)
else:
self.sparesb = gtk.Label(str(origrequest.spares))
self.levelcombo = gtk.Label(origrequest.level)
maintable.attach(self.levelcombo, 1, 2, row, row + 1)
row = row + 1
# raid members
lbl=createAlignedLabel(_("_RAID Members:"))
maintable.attach(lbl, 0, 1, row, row + 1)
# XXX need to pass in currently used partitions for this device
(self.raidlist, sw) = self.createAllowedRaidPartitionsList(availraidparts,
origrequest.devices,
origrequest.exists)
lbl.set_mnemonic_widget(self.raidlist)
self.raidlist.set_size_request(275, 80)
maintable.attach(sw, 1, 2, row, row + 1)
row = row + 1
if origrequest.exists:
self.raidlist.set_sensitive(False)
# number of spares - created widget above
lbl = createAlignedLabel(_("Number of _spares:"))
maintable.attach(lbl, 0, 1, row, row + 1)
maintable.attach(self.sparesb, 1, 2, row, row + 1)
lbl.set_mnemonic_widget(self.sparesb)
row = row + 1
# format or not?
self.formatButton = None
self.fsoptionsDict = {}
if not format.exists and not origrequest.exists:
self.formatButton = gtk.CheckButton(_("_Format partition?"))
if not format.type:
self.formatButton.set_active(1)
else:
self.formatButton.set_active(0)
# it only makes sense to show this for preexisting RAID
if origrequest.exists:
maintable.attach(self.formatButton, 0, 2, row, row + 1)
row = row + 1
# checkbutton for encryption using dm-crypt/LUKS
if origrequest.format.type == "luks":
self.lukscb.set_active(1)
else:
self.lukscb.set_active(0)
maintable.attach(self.lukscb, 0, 2, row, row + 1)
row = row + 1
else:
(row, self.fsoptionsDict) = createPreExistFSOptionSection(origrequest, maintable, row, self.mountCombo, self.storage, luksdev=luksdev)
# put main table into dialog
dialog.vbox.pack_start(maintable)
dialog.show_all()
self.dialog = dialog
return
def allow_ok_button(self, selected_count):
"""
Determine if the OK button should be enabled.
The OK button is enabled whenever at least one row is selected.
"""
self.ok_button.set_sensitive(selected_count > 0)
def _total_selected_members(self, path=None):
"""
Determine how many raid members are checked (selected) at the moment.
If path is given it points to the row where the toggle state is about to
change. Unfortunately its value is opposite of the value it is *going to
have* after the callback thus the complication below.
"""
ret = 0
model = self.raidlist.get_model()
iter = model.get_iter_first()
toggled_iter = None
if path:
toggled_iter = model.get_iter(path)
while iter:
val = model.get_value(iter, 0)
if toggled_iter and \
model.get_value(toggled_iter, 1) == \
model.get_value(iter, 1):
# this is being toggled, negate the value:
if not val:
ret += 1
else:
if val:
ret += 1
iter = model.iter_next(iter)
return ret
def raidlist_toggle_callback(self, data, path):
level_index = self.levelcombo.get_active()
raidlevel = self.levelcombo.get_model()[level_index][0]
selected_count = self._total_selected_members(path)
self.allow_ok_button(selected_count)
self._adjust_spares_button(raidlevel, selected_count)
return 1