diff options
author | Mike Fulbright <msf@redhat.com> | 2002-06-27 07:20:36 +0000 |
---|---|---|
committer | Mike Fulbright <msf@redhat.com> | 2002-06-27 07:20:36 +0000 |
commit | 574f813b90527b45d293460a833116c19790f954 (patch) | |
tree | 641586b657c95da19ed30a5562f04d7c7c533abd | |
parent | 87eb95ea46fc8d4d1163dc970b2229f42d6affa0 (diff) | |
download | anaconda-574f813b90527b45d293460a833116c19790f954.tar.gz anaconda-574f813b90527b45d293460a833116c19790f954.tar.xz anaconda-574f813b90527b45d293460a833116c19790f954.zip |
Start of RAID clone tool
-rw-r--r-- | iw/partition_gui.py | 78 | ||||
-rw-r--r-- | iw/raid_dialog_gui.py | 244 | ||||
-rw-r--r-- | partIntfHelpers.py | 64 | ||||
-rw-r--r-- | partitions.py | 29 |
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 |