From cef1d2907260cdd57fcb105a69f7551bb5538cd7 Mon Sep 17 00:00:00 2001 From: David Lehman Date: Thu, 26 Jul 2012 21:13:32 -0500 Subject: [PATCH 6/6] Add an IR for the devices in the custom UI. --- pyanaconda/ui/gui/spokes/custom.py | 369 +++++++++++++++++++++++++++++------- 1 files changed, 303 insertions(+), 66 deletions(-) diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 2793957..2bddb9d 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -125,8 +125,45 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._current_selector = None self._when_create_text = "" + self.ui_devices = [] + self._deleted_devices = [] + self._free_space = Size(bytes=0) def apply(self): + # schedule actions for device removals, resizes + for removed in self._deleted_devices: + if removed.device_id is None: + # created here, destroyed here -> NOOP + continue + + device = self.storage.devicetree.getDeviceByName(removed.name) + self.storage.destroyDevice(device) + + for ui_device in self.ui_devices: + if ui_device.device_id is None: + # we're creating a device + + # this isn't a leaf device + if ui_device.create_type is None: + continue + + self.storage.newUIDevice(ui_device.create_type, + ui_device.size, + encrypted=ui_device.encrypted, + level=ui_device.raid_level, + disks=self.storage.disks) + + # this is a device that's already in the tree + device = self.storage.devicetree.getDeviceByName(ui_device.name) + if ui_device.resizable and ui_device.size != device.size: + # schedule a resize action for the device and its format + pass + + # XXX How can we handle edits? + # + # Doesn't matter for now since we don't have support for editing + # devices. + self.storage.setUpBootLoader() StorageChecker.run(self) @@ -147,7 +184,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._configButton = self.builder.get_object("configureButton") def initialize(self): - from pyanaconda.storage.devices import DiskDevice from pyanaconda.storage.formats.fs import FS NormalSpoke.initialize(self) @@ -205,7 +241,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): from pyanaconda.storage.devices import DiskDevice return [d for d in self.storage.unusedDevices if d.disks and not isinstance(d, DiskDevice)] - def _currentFreeSpace(self): + def _setCurrentFreeSpace(self): """Add up all the free space on selected disks and return it as a Size.""" totalFree = Size(bytes=0) @@ -214,7 +250,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): for chunk in tup: totalFree += chunk - return totalFree + self._free_space = totalFree def _currentTotalSpace(self): """Add up the sizes of all selected disks and return it as a Size.""" @@ -231,7 +267,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._totalSpaceLabel = self.builder.get_object("totalSpaceLabel") self._summaryButton = self.builder.get_object("summary_button") - self._availableSpaceLabel.set_text(str(self._currentFreeSpace())) + self._availableSpaceLabel.set_text(str(self._free_space)) self._totalSpaceLabel.set_text(str(self._currentTotalSpace())) summaryLabel = self._summaryButton.get_children()[0] @@ -245,7 +281,12 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): def refresh(self): NormalSpoke.refresh(self) + self.ui_devices = get_ui_devices(self.storage) self._do_refresh() + + # update our free space number based on Storage + self._setCurrentFreeSpace() + self._updateSpaceDisplay() def _do_refresh(self): @@ -258,12 +299,43 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # Now it's time to populate the accordion. + # set up a dict with name keys for the ui device list + ui_device_dict = {} + for device in self.ui_devices: + ui_device_dict[device.name] = device + # We can only have one page expanded at a time. did_expand = False - unused = self._unusedDevices() - new_devices = [d for d in self.storage.devicetree.leaves if d not in unused and not d.exists] + unused = [ui_device_dict[d.name] for d in self._unusedDevices() + if d.name in ui_device_dict and + ui_device_dict[d.name].device_id is not None] + new_devices = [d for d in self.ui_devices if d.device_id is None] + roots = self.storage.roots[:] + ui_roots = [] + for root in roots: + name = root.name + swaps = [] + mounts = {} + for (mountpoint, device) in root.mounts.items(): + ui_device = ui_device_dict.get(device.name) + if not ui_device or ui_device.id is None: + continue + + mounts[mountpoint] = ui_device + + for device in root.swaps: + ui_device = ui_device_dict.get(device.name) + if not ui_device or ui_device.id is None: + continue + + swaps.append(ui_device) + + if not swaps and not mounts: + continue + + ui_roots.append(UIRoot(name=name, mounts=mounts, swaps=swaps)) # If we've not yet run autopart, add an instance of CreateNewPage. This # ensures it's only added once. @@ -278,32 +350,23 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): label = self.builder.get_object("whenCreateLabel") label.set_text(self._when_create_text % (productName, productVersion)) else: - swaps = [d for d in new_devices if d in self.storage.swaps] - mounts = dict([(d.format.mountpoint, d) for d in new_devices if getattr(d.format, "mountpoint", None)]) - new_root = Root(mounts=mounts, swaps=swaps, name=new_install_name) - roots.insert(0, new_root) + swaps = [d for d in new_devices if d.fstype == "swap"] + mounts = dict([(d.mountpoint, d) for d in new_devices if d.mountpoint]) + new_root = UIRoot(mounts=mounts, swaps=swaps, name=new_install_name) + ui_roots.insert(0, new_root) # Add in all the existing (or autopart-created) operating systems. - for root in roots: - if root.device and root.device not in self.storage.devices: - continue - + for root in ui_roots: page = Page() page.pageTitle = root.name for device in root.swaps: - if device not in self.storage.devices: - continue - selector = page.addDevice("Swap", device.size, None, self.on_selector_clicked) selector._device = device selector._root = root for (mountpoint, device) in root.mounts.iteritems(): - if device not in self.storage.devices: - continue - - selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, device.size, mountpoint, self.on_selector_clicked) + selector = page.addDevice(self._mountpointName(mountpoint) or device.fstype, device.size, mountpoint, self.on_selector_clicked) selector._device = device selector._root = root @@ -320,14 +383,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): page.pageTitle = _("Unknown") for u in unused: - selector = page.addDevice(u.format.name, u.size, None, self.on_selector_clicked) + selector = page.addDevice(u.fstype, u.size, None, self.on_selector_clicked) selector._device = u selector._root = None page.show_all() self._accordion.addPage(page, cb=self.on_page_clicked) - if not did_expand and self._current_selector and unused == self._current_selector._root: + if not did_expand and self._current_selector and self._current_selector._root is None: did_expand = True self._accordion.expandPage(page.pageTitle) @@ -361,8 +424,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): device = selector._device - if hasattr(device.format, "label") and labelEntry.get_text(): - device.format.label = labelEntry.get_text() + if labelEntry.get_text(): + device.label = labelEntry.get_text() def _populate_right_side(self, selector): encryptCheckbox = self.builder.get_object("encryptCheckbox") @@ -378,15 +441,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): selectedDeviceLabel.set_text(selector.props.name) selectedDeviceDescLabel.set_text(self._description(selector.props.name)) - labelEntry.set_text(getattr(device.format, "label", "") or "") - labelEntry.set_sensitive(getattr(device.format, "labelfsProg", "") != "") + labelEntry.set_text(device.label or "") + can_label = getattr(getFormat(device.fstype), "labelfsProg", "") != "" + labelEntry.set_sensitive(can_label) if labelEntry.get_sensitive(): labelEntry.props.has_tooltip = False else: labelEntry.set_tooltip_text(_("This file system does not support labels.")) - sizeSpinner.set_range(device.minSize, device.maxSize) + sizeSpinner.set_range(device.min_size, device.max_size) sizeSpinner.set_value(device.size) sizeSpinner.set_sensitive(device.resizable) @@ -398,17 +462,19 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): encryptCheckbox.set_active(device.encrypted) # FIXME: What do we do if we can't figure it out? - if device.type == "lvmlv": + if device.type == "LVM Logical Volume": typeCombo.set_active(1) - elif device.type in ["dm-raid array", "mdarray"]: + elif device.type == "Software RAID": typeCombo.set_active(2) - elif device.type == "partition": + elif device.type == "Partition": typeCombo.set_active(3) + elif device.type.startswith("BTRFS"): + typeCombo.set_active(0) # FIXME: What do we do if we can't figure it out? model = fsCombo.get_model() for i in range(0, len(model)): - if model[i][0] == device.format.name: + if model[i][0] == device.fstype: fsCombo.set_active(i) break @@ -455,32 +521,32 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): mountpoint in self.storage.mountpoints.keys(): return - if not size: - # no size specified, so use the default size - size = None - - if self.data.autopart.type == AUTOPART_TYPE_PLAIN: - # create a partition for this new filesystem - size_mb = float(size.convertTo(spec="mb")) - device = self.storage.newPartition(size=size_mb, - fmt_type=fstype, - mountpoint=mountpoint) - self.storage.createDevice(device) - try: - doPartitioning(self.storage) - except StorageError as e: - actions = self.storage.devicetree.findActions(device=device) - for a in reversed(actions): - self.storage.devicetree.cancelAction(a) - else: - self._do_refresh() + if self.data.autopart.type == AUTOPART_TYPE_LVM and \ + mountpoint != "/boot/efi": + device_class = UILogicalVolume + elif self.data.autopart.type == AUTOPART_TYPE_BTRFS and \ + fstype != "swap" and \ + not mountpoint.startswith("/boot"): + device_class = UIBTRFSSubVolume + else: + device_class = UIPartition + + device = device_class(mountpoint=mountpoint, size=size, fstype=fstype) + self.ui_devices.append(device) + self._free_space -= Size(spec="%f MB" % device.disk_space) + self._do_refresh() def _destroy_device(self, device): + self.ui_devices.remove(device) + self._deleted_devices.append(device) + + if device.type == "Partition": + self._free_space += Size(spec="%f MB" % device.size) + # if this device has parents with no other children, remove them too - parents = device.parents[:] - self.storage.destroyDevice(device) - for parent in parents: - if parent.kids == 0 and not parent.isDisk: + for parent in device.parents: + all_parents = [p for d in self.ui_devices for p in d.parents] + if parent not in all_parents and parent.type != "Disk": self._destroy_device(parent) def _remove_from_root(self, root, device): @@ -530,12 +596,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._remove_from_root(root, device) self._destroy_device(device) - - # If the root is now empty, remove it. Devices from the Unused page - # will have no root. - if root and root in self.storage.roots and len(root.swaps + root.mounts.values()) == 0: - self.storage.roots.remove(root) - self._update_ui_for_removals() elif self._accordion.currentPage(): # This is a complete installed system. Thus, we first need to confirm @@ -562,11 +622,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): for device in root.swaps + root.mounts.values(): self._destroy_device(device) - # Remove the root entirely. This will cause the empty Page to be - # deleted from the left hand side. - if root in self.storage.roots: - self.storage.roots.remove(root) - self._update_ui_for_removals() def on_summary_clicked(self, button): @@ -616,6 +671,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # Then do autopartitioning. We do not do any clearpart first. This is # custom partitioning, so you have to make your own room. # FIXME: Handle all the autopart exns here. + # UIDeviceFIXME: we can't run autopart. we have to pretend. self.data.autopart.autopart = True self.data.autopart.execute(self.storage, self.data, self.instclass) self.data.autopart.autopart = False @@ -641,3 +697,184 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._optionsNotebook.set_current_page(2) elif text == _("Standard Partition"): self._optionsNotebook.hide() + +class UIDevice(object): + type = "Generic Device" + raid_levels = [] + create_type = None + + def __init__(self, size=None, mountpoint=None, fstype=None, name=None, + raid_level=None, parents=None, exists=False, encrypted=False, + resizable=False, min_size=None, max_size=None, + device_id=None): + self.size = size + self.mountpoint = mountpoint + self.fstype = fstype + self.name = name + self.raid_level = raid_level + self.parents = parents or [] + self.exists = exists + self.encrypted = encrypted + + self.resizable = resizable + self.min_size = min_size + self.max_size = max_size + + self.device_id = device_id + + @property + def disk_space(self): + space = self.size + if self.raid_level in ("raid1", "raid10"): + space = self.size * len(self.parents) + elif self.raid_level == "raid5": + space = self.size * (float(len(parents)) / (len(parents) - 1)) + elif self.raid_level == "raid6": + space = self.size * (float(len(parents)) / (len(parents) - 2)) + + return space + +class UIDisk(UIDevice): + type = "Disk" + +class UIPartition(UIDevice): + type = "Partition" + create_type = AUTOPART_TYPE_PLAIN + +class UIVolumeGroup(UIDevice): + type = "LVM Volume Group" + +class UILogicalVolume(UIDevice): + type = "LVM Logical Volume" + raid_levels = ["linear", "raid0", "raid1", "raid10"] + create_type = AUTOPART_TYPE_LVM + +class UIRAIDArray(UIDevice): + type = "Software RAID" + raid_levels = ["raid0", "raid1", "raid5", "raid10"] + +class UIBTRFSVolume(UIDevice): + type = "BTRFS" + raid_levels = ["linear", "raid0", "raid1", "raid10"] + create_type = AUTOPART_TYPE_BTRFS + +class UIBTRFSSubVolume(UIDevice): + type = "BTRFS Sub-Volume" + create_type = AUTOPART_TYPE_BTRFS + +""" + UI for device editing + + - convert current StorageDevice tree to UIDevice tree + - modify UIDevice tree in UI + - generate a list of actions to represent the changes +""" +def get_ui_device_class(device): + ui_class = None + if device.isDisk: + ui_class = UIDisk + elif device.type == "partition": + ui_class = UIPartition + elif device.type == "mdarray": + ui_class = UIRAIDArray + elif device.type == "lvmvg": + ui_class = UIVolumeGroup + elif device.type == "lvmlv": + ui_class = UILogicalVolume + elif device.type == "luks/dm-crypt": + ui_class = get_ui_device_type(device.parents[0]) + + print "get_ui_device_class returning %s for device %s" % (ui_class, device) + return ui_class + +def get_ui_device_raid_level(device): + level = None + if device.type == "mdarray": + if device.level == mdraid.RAID0: + level = "raid0" + elif device.level == mdraid.RAID1: + level = "raid1" + elif device.level == mdraid.RAID5: + level = "raid5" + elif device.level == mdraid.RAID6: + level = "raid6" + elif device.level == mdraid.RAID10: + level = "raid10" + elif device.type == "lvmlv": + # TODO: implement striping and mirroring in LVMLogicalVolumeDevice + pass + elif device.type == "btrfs volume": + level = device.dataLevel + + return level + +def get_ui_parents(storage_device, ui_devices): + ui_parents = [] + for parent in storage_device.parents: + for u in ui_devices: + if u.name == parent.name: + ui_parents.append(u) + + return ui_parents + +def get_ui_devices(storage): + devices = storage.devicetree.devices[:] + ui_devices = [] + while devices: + roots = [d for d in devices if + not [p for p in d.parents if p in devices]] + for device in roots: + ui_class = get_ui_device_class(device) + devices.remove(device) + if ui_class is None: + continue + + min_size = device.size + max_size = device.size + resizable = False + if device.format.type == "luks" and \ + storage.devicetree.getChildren(device): + continue + elif device.type == "luks/dm-crypt": + encrypted = True + raid_level = get_ui_device_raid_level(device.parents[0]) + exists = device.parents[0].exists + devices.remove(device.parents[0]) + parents = get_ui_parents(device.parents[0], ui_devices) + else: + encrypted = False + raid_level = get_ui_device_raid_level(device) + exists = device.exists + parents = get_ui_parents(device, ui_devices) + if device.resizable: + resizable = True + min_size = device.minSize + max_size = device.maxSize + + ui_device = ui_class(name=device.name, + size=device.size, + fstype=device.format.type, + mountpoint=getattr(device.format, + "mountpoint", None), + raid_level=raid_level, + encrypted=encrypted, + parents=parents, + exists=exists, + resizable=resizable, + min_size=min_size, + max_size=max_size, + device_id=device.id) + ui_devices.append(ui_device) + + return ui_devices + +class UIRoot(object): + def __init__(self, name=None, mounts=None, swaps=None): + self.name = name + self.mounts = mounts or {} + self.swaps = swaps or [] + + @property + def devices(self): + return self.mounts.values() + self.swaps[:] + -- 1.7.7.6