summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--iw/partition_gui.py78
-rw-r--r--iw/raid_dialog_gui.py244
-rw-r--r--partIntfHelpers.py64
-rw-r--r--partitions.py29
4 files changed, 392 insertions, 23 deletions
diff --git a/iw/partition_gui.py b/iw/partition_gui.py
index e12289342..265b867b4 100644
--- a/iw/partition_gui.py
+++ b/iw/partition_gui.py
@@ -462,6 +462,7 @@ class DiskTreeModel(gtk.TreeStore):
# not found the partition
raise RuntimeError, "could not find partition"
+ """ returns partition 'id' of current selection in tree """
def getCurrentPartition(self):
selection = self.view.get_selection()
model, iter = selection.get_selected()
@@ -470,7 +471,30 @@ class DiskTreeModel(gtk.TreeStore):
pyobject = self.titleSlot['PyObject']
try:
- return self.get_value(iter, pyobject)
+ val = self.get_value(iter, pyobject)
+ if type(val) == type("/dev/"):
+ if val[:5] == '/dev/':
+ return None
+
+ return val
+ except:
+ return None
+
+ """ Return name of current selected drive (if a drive is highlighted) """
+ def getCurrentDevice(self):
+ selection = self.view.get_selection()
+ model, iter = selection.get_selected()
+ if not iter:
+ return None
+
+ pyobject = self.titleSlot['PyObject']
+ try:
+ val = self.get_value(iter, pyobject)
+ if type(val) == type("/dev/"):
+ print val
+ if val[:5] == '/dev/':
+ return val
+ return None
except:
return None
@@ -751,6 +775,7 @@ class PartitionWindow(InstallWindow):
# add a parent node to the tree
parent = self.tree.append(drvparent)
self.tree[parent]['Device'] = '/dev/%s' % (drive,)
+ self.tree[parent]['PyObject'] = str('/dev/%s' % (drive,))
sectorsPerCyl = disk.dev.heads * disk.dev.sectors
extendedParent = None
@@ -881,9 +906,17 @@ class PartitionWindow(InstallWindow):
def deleteCb(self, widget):
curselection = self.tree.getCurrentPartition()
- if doDeletePartitionByRequest(self.intf, self.partitions, curselection):
- self.refresh()
-
+ if curselection:
+ if doDeletePartitionByRequest(self.intf, self.partitions, curselection):
+ self.refresh()
+ else:
+ curdevice = self.tree.getCurrentDevice()
+ if curdevice and len(curdevice) > 5:
+ if doDeletePartitionsByDevice(self.intf, self.partitions, self.diskset, curdevice[5:]):
+ self.refresh()
+ else:
+ return
+
def resetCb(self, *args):
if not confirmResetPartitionState(self.intf):
return
@@ -1145,9 +1178,7 @@ class PartitionWindow(InstallWindow):
"You currently have %s software RAID "
"partition(s) free to use.\n\n") % (constants.productName, len(availraidparts))
- haveenuf = len(availraidparts) > 1
-
- if not haveenuf:
+ if len(availraidparts) < 2:
lbltxt = lbltxt + _("To use RAID you must first "
"create least two partitions of type "
"'software RAID'. Then you can "
@@ -1174,12 +1205,15 @@ class PartitionWindow(InstallWindow):
"RAID device [default=/dev/md%s].") % newminor)
radioBox.pack_start(doRAIDclone, gtk.FALSE, gtk.FALSE, padding=10)
- if not haveenuf:
- createRAIDpart.set_active(1)
- createRAIDdev.set_sensitive(0)
- doRAIDclone.set_sensitive(0)
- else:
- createRAIDdev.set_active(1)
+ createRAIDpart.set_active(1)
+ doRAIDclone.set_sensitive(0)
+ createRAIDdev.set_sensitive(0)
+ if len(availraidparts) > 0 :
+ doRAIDclone.set_sensitive(1)
+
+ if len(availraidparts) > 1:
+ createRAIDdev.set_active(1)
+ createRAIDdev.set_sensitive(1)
align = gtk.Alignment(0.5, 0.0)
align.add(radioBox)
@@ -1201,7 +1235,21 @@ class PartitionWindow(InstallWindow):
elif createRAIDdev.get_active():
self.editRaidRequest(request, isNew=1)
else:
- self.intf.messageWindow("not yet", "Cloning not allowed")
+ cloneDialog = raid_dialog_gui.RaidCloneDialog(self.partitions,
+ self.diskset,
+ self.intf,
+ self.parent)
+ if cloneDialog is None:
+ self.intf.messageWindow(_("Couldn't Create Drive Clone Editor"),
+ _("The drive clone editor could not "
+ "be created for some reason."))
+ return
+
+ while 1:
+ rc = cloneDialog.run()
+
+ cloneDialog.destroy()
+ return
def viewButtonCB(self, widget):
@@ -1218,7 +1266,7 @@ class PartitionWindow(InstallWindow):
self.diskset.openDevices()
self.partitions = partitions
- self.show_uneditable = 0
+ self.show_uneditable = 1
checkForSwapNoMatch(self.intf, self.diskset, self.partitions)
diff --git a/iw/raid_dialog_gui.py b/iw/raid_dialog_gui.py
index c6b3e4d5d..a8d8507f7 100644
--- a/iw/raid_dialog_gui.py
+++ b/iw/raid_dialog_gui.py
@@ -410,3 +410,247 @@ class RaidEditor:
dialog.show_all()
self.dialog = dialog
return
+
+
+
+class RaidCloneDialog:
+ def createDriveList(self, diskset):
+
+ store = gtk.ListStore(gobject.TYPE_STRING)
+ view = gtk.TreeView(store)
+
+ sw = gtk.ScrolledWindow()
+ sw.add(view)
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ sw.set_shadow_type(gtk.SHADOW_IN)
+
+ drives = diskset.disks.keys()
+ drives.sort()
+
+ for drive in drives:
+ iter = store.append()
+ store.set_value(iter, 0, drive)
+
+ view.set_property("headers-visible", gtk.FALSE)
+
+ col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
+ view.append_column(col)
+
+ return (sw, view)
+
+ def getInterestingRequestsForDrive(self, drive):
+ allrequests = self.partitions.getRequestsByDevice(self.diskset, drive)
+
+ # remove extended partitions
+ requests = []
+ for req in allrequests:
+ try:
+ part = partedUtils.get_partition_by_name(self.diskset.disks,
+ req.device)
+ except:
+ part = None
+
+ if part and part.type & parted.PARTITION_EXTENDED:
+ continue
+
+ requests.append(req)
+
+ return requests
+
+ def sanityCheckSourceDrive(self):
+ # first see if it has any non-software RAID partitions
+ requests = self.getInterestingRequestsForDrive(self.sourceDrive)
+
+ errmsg1 = _("The source drive has no partitions to be cloned. "
+ "You must first defined partitions of type "
+ "'software RAID' on this drive before it can be cloned.")
+ if not requests or len(requests) == 0:
+ self.intf.messageWindow(_("Source Drive Error"), errmsg1)
+ return 0
+
+ for req in requests:
+ if not req.fstype or req.fstype.getName() != "software RAID":
+ self.intf.messageWindow(_("Source Drive Error"),
+ _("The source drive selected has "
+ "partitions on it which are not of "
+ "type 'software RAID'.\n\n"
+ "These "
+ "partitions will have to removed "
+ "before this drive can be cloned. "))
+ return 0
+
+ for req in requests:
+ if not req.drive or req.drive[0] != self.sourceDrive or len(req.drive) > 1:
+ self.intf.messageWindow(_("Source Drive Error"),
+ _("The source drive selected has "
+ "partitions which are not "
+ "constrained to the drive /dev/%s.\n\n"
+ "These partitions will have to "
+ "removed or restricted to this "
+ "drive "
+ "before this drive can be cloned. ")
+ %(self.sourceDrive,))
+ return 0
+
+ for req in requests:
+ if self.partitions.isRaidMember(req):
+ self.intf.messageWindow(_("Source Drive Error"),
+ _("The source drive selected has "
+ "software RAID partition(s) which "
+ "are members of an active "
+ "software RAID device.\n\n"
+ "These partitions will have to "
+ "removed before this drive "
+ "can be cloned."))
+ return 0
+
+ return 1
+
+ def sanityCheckTargetDrives(self):
+ if self.targetDrives is None or len(self.targetDrives) < 1:
+ self.intf.messageWindow(_("Target Drive Error"),
+ _("Please select the target drives "
+ "for the clone operation."))
+ return 0
+
+ if self.sourceDrive in self.targetDrives:
+ self.intf.messageWindow(_("Target Drive Error"),
+ _("The source drive /dev/%s cannot be "
+ "selected as a target drive as well.") % (self.sourceDrive,))
+ return 0
+
+ for drive in self.targetDrives:
+ requests = self.getInterestingRequestsForDrive(drive)
+ for req in requests:
+ rc = partIntfHelpers.isNotChangable(req, self.partitions)
+ if rc:
+ self.intf.messageWindow(_("Target Drive Error"),
+ _("The target drive /dev/%s "
+ "has a partition which cannot "
+ "be removed for the following "
+ "reason:\n\n\"%s\"\n\n"
+ "This partition must be removed "
+ "(if possible) before "
+ "this drive can be a target.") %
+ (drive, rc % (_("delete"),)))
+ return 0
+
+ return 1
+
+ def targetSelectFunc(self, model, path, iter):
+ self.targetDrives.append(model.get_value(iter,0))
+
+ def run(self):
+ if self.dialog is None:
+ return None
+
+ while 1:
+ rc = self.dialog.run()
+
+ # user hit cancel, do nothing
+ if rc == 2:
+ self.destroy()
+ return None
+
+ # see what drive they selected as the source
+ selection = self.sourceView.get_selection()
+ (model, iter) = selection.get_selected()
+ if iter is None:
+ self.intf.messageWindow(_("Error"),
+ _("Please select a source drive."))
+ continue
+
+ self.sourceDrive = model.get_value(iter, 0)
+
+ # sanity check it
+ if not self.sanityCheckSourceDrive():
+ continue
+
+
+ # now get target drive(s)
+ self.targetDrives = []
+ selection = self.targetView.get_selection()
+ selection.selected_foreach(self.targetSelectFunc)
+
+ # sanity check it
+ if not self.sanityCheckTargetDrives():
+ continue
+
+
+ # everything ok, break out
+ break
+
+ return None
+
+ def destroy(self):
+ if self.dialog:
+ self.dialog.destroy()
+
+ self.dialog = None
+
+ def __init__(self, partitions, diskset, intf, parent):
+ self.partitions = partitions
+ self.diskset = diskset
+ self.intf = intf
+ self.parent = parent
+
+ self.dialog = None
+ self.dialog = gtk.Dialog(_("Make RAID Device"), self.parent)
+ self.dialog.set_size_request(500, 400)
+ gui.addFrame(self.dialog)
+ self.dialog.add_button('gtk-cancel', 2)
+ self.dialog.add_button('gtk-ok', 1)
+ self.dialog.set_position(gtk.WIN_POS_CENTER)
+
+ # present list of drives as source
+ vbox = gtk.VBox()
+
+ lbl = gui.WrappingLabel(_("Clone Drive Tool\n\n"
+ "This tool allows you to significantly "
+ "reduce the amount of effort required "
+ "to setup RAID arrays. The idea is to "
+ "take a source drive which has been "
+ "prepared with the desired partitioning "
+ "layout, and clone this layout onto other "
+ "similar sized drives. Then a RAID device "
+ "can be created.\n\n"
+ "NOTE: The source drive must have "
+ "partitions which are restricted to be on "
+ "that drive only, and can only contain "
+ "unused software RAID partitions. Other "
+ "partition types are not allowed.\n\n"
+ "EVERYTHING on the target drive(s) will be "
+ "destroyed by this process."))
+ vbox.pack_start(lbl)
+
+ box = gtk.HBox()
+
+ lbl = gtk.Label(_("Source Drive:"))
+ lbl.set_alignment(0.0, 0.0)
+ box.pack_start(lbl, padding=5)
+ (sw, self.sourceView) = self.createDriveList(diskset)
+ selection = self.sourceView.get_selection()
+ selection.set_mode(gtk.SELECTION_SINGLE)
+ box.pack_start(sw)
+
+ lbl = gtk.Label(_("Target Drive(s):"))
+ lbl.set_alignment(0.0, 0.0)
+ box.pack_start(lbl, padding=5)
+ (sw, self.targetView) = self.createDriveList(diskset)
+ selection = self.targetView.get_selection()
+ selection.set_mode(gtk.SELECTION_MULTIPLE)
+ box.pack_start(sw)
+
+ frame = gtk.Frame(_("Drives"))
+ frame.add(box)
+ vbox.pack_start(frame)
+
+ # put contents into dialog
+ self.dialog.vbox.pack_start(vbox)
+
+ self.dialog.show_all()
+
+ return
+
+
+
diff --git a/partIntfHelpers.py b/partIntfHelpers.py
index 71d5f548e..dba60552d 100644
--- a/partIntfHelpers.py
+++ b/partIntfHelpers.py
@@ -18,7 +18,6 @@
"""Helper functions shared between partitioning interfaces."""
import string
-from rhpl.translate import _
from constants import *
import partedUtils
import parted
@@ -26,6 +25,8 @@ import fsset
import iutil
import partRequests
+from rhpl.translate import _
+
def sanityCheckVolumeGroupName(volname):
"""Make sure that the volume group name doesn't contain invalid chars."""
badNames = ['lvm']
@@ -124,7 +125,8 @@ def isNotChangable(request, requestlist):
return None
-def doDeletePartitionByRequest(intf, requestlist, partition):
+def doDeletePartitionByRequest(intf, requestlist, partition,
+ confirm=1, quiet=0):
"""Delete a partition from the request list.
intf is the interface
@@ -154,10 +156,11 @@ def doDeletePartitionByRequest(intf, requestlist, partition):
ret = requestlist.containsImmutablePart(partition)
if ret:
- intf.messageWindow(_("Unable To Delete"),
- _("You cannot delete this "
- "partition, as it is an extended partition "
- "which contains %s") %(ret))
+ if not quiet:
+ intf.messageWindow(_("Unable To Delete"),
+ _("You cannot delete this "
+ "partition, as it is an extended partition "
+ "which contains %s") %(ret))
return 0
# see if device is in our partition requests, remove
@@ -169,10 +172,11 @@ def doDeletePartitionByRequest(intf, requestlist, partition):
if request:
state = isNotChangable(request, requestlist)
if state is not None:
- intf.messageWindow(_("Unable To Delete"), state % ("delete",))
+ if not quiet:
+ intf.messageWindow(_("Unable To Delete"), state % ("delete",))
return (None, None)
- if not confirmDeleteRequest(intf, request):
+ if confirm and not confirmDeleteRequest(intf, request):
return 0
if request.getPreExisting():
@@ -208,6 +212,50 @@ def doDeletePartitionByRequest(intf, requestlist, partition):
del partition
return 1
+def doDeletePartitionsByDevice(intf, requestlist, diskset, device):
+ """ Remove all partitions currently on device """
+ rc = intf.messageWindow(_("Confirm Delete"),
+ _("You are about to delete all partitions on "
+ "the device '/dev/%s'.") % (device,),
+ type="custom",
+ custom_buttons=[_("Cancel"), _("_Delete")])
+
+ if not rc:
+ return
+
+ requests = requestlist.getRequestsByDevice(diskset, device)
+ if not requests:
+ return
+
+ # XXX assumes all requests are due to a real partition device
+ notdeleted = []
+ for req in requests:
+ try:
+ part = partedUtils.get_partition_by_name(diskset.disks, req.device)
+ rc = doDeletePartitionByRequest(intf, requestlist, part,
+ confirm=0, quiet=1)
+
+ # not sure why it returns both '0' and '(None, None)' on failure
+ if not rc or rc == (None, None):
+ notdeleted.append(req)
+ except:
+ notdeleted.append(req)
+
+ # see if we need to report any failures - some were because we removed
+ # an extended partition which contained other members of our delete list
+ outlist = ""
+ for req in notdeleted:
+ newreq = requestlist.getRequestByID(req.uniqueID)
+ if newreq:
+ outlist = outlist + "\t/dev/%s\n" % (newreq.device,)
+
+ if outlist != "":
+ intf.messageWindow(_("Notice"),
+ _("The following partitions were not deleted "
+ "because they are in use:\n\n%s") % outlist)
+
+ return 1
+
def doEditPartitionByRequest(intf, requestlist, part):
"""Edit a partition from the request list.
diff --git a/partitions.py b/partitions.py
index a25feeb04..5ef9e43bc 100644
--- a/partitions.py
+++ b/partitions.py
@@ -324,6 +324,32 @@ class Partitions:
return request
return None
+
+ def getRequestsByDevice(self, diskset, device):
+ """Find and return the requests on a given device (like 'hda')."""
+ if device is None:
+ return None
+
+ drives = diskset.disks.keys()
+ if device not in drives:
+ return None
+
+ rc = []
+ disk = diskset.disks[device]
+ part = disk.next_partition()
+ while part:
+ dev = partedUtils.get_partition_name(part)
+ request = self.getRequestByDeviceName(dev)
+
+ if request:
+ rc.append(request)
+ part = disk.next_partition(part)
+
+ if len(rc) > 0:
+ return rc
+ else:
+ return None
+
def getRequestByVolumeGroupName(self, volname):
"""Find and return the request with the given volume group name."""
if volname is None:
@@ -989,6 +1015,9 @@ class Partitions:
if self.isRaidMember(request):
return _("a partition which is a member of a RAID array.")
+ if self.isLVMVolumeGroupMember(request):
+ return _("a partition which is a member of a LVM Volume Group.")
+
part = disk.next_partition(part)
return None