This set fixes several bugs, and then several more that I hit while testing the fixes. I spent all day today testing these, doing as many combinations as I could think of of custom and automatic partitioning, encrypted and not, btrfs, lvm, md, &c. I'm getting pretty bored with installing fedora all the time.
If the user selects btrfs as device type and there is a new btrfs volume defined, populate the raid options based on that volume's raid settings. --- pyanaconda/ui/gui/spokes/custom.py | 10 +++++++++- 1 files changed, 9 insertions(+), 1 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index a3242df..630614d 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -2105,7 +2105,15 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): test_fmt = getFormat("btrfs") include_btrfs = test_fmt.supported and test_fmt.formattable fs_type_sensitive = False - raid_level = "single" + with ui_storage_logger(): + factory = self.__storage.getDeviceFactory(DEVICE_TYPE_BTRFS, 0) + container = self.__storage.getContainer(factory) + + if container: + raid_level = container.dataLevel or "single" + else: + # here I suppose we could alter the default based on disk count + raid_level = "single" elif new_type == DEVICE_TYPE_MD: raid_level = "raid0"
In this case what's really being done is we're creating a new device with the old parameters since we failed to apply the new settings. Either way, we shouldn't be passing the new/failed encryption, raid level, or disk set when trying to recreate the previous device setup. --- pyanaconda/ui/gui/spokes/custom.py | 9 ++++----- 1 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 630614d..f33d34e 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -1097,14 +1097,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # in this case we have removed the old device so we now have # to re-create it try: - # XXX FIXME: pass old raid level -- not new one - self._replace_device(device_type, device.size, - disks=disks, + self._replace_device(current_device_type, device.size, + disks=old_disk_set, fstype=device.format.type, mountpoint=old_mountpoint, label=old_label, - raid_level=raid_level, - encrypted=encrypted, + raid_level=old_raid_level, + encrypted=prev_encrypted, selector=selector) except StorageError as e: # failed to recover.
--- pyanaconda/bootloader.py | 22 ++++++++++++++++++---- pyanaconda/kickstart.py | 1 + pyanaconda/storage/__init__.py | 6 +++++- pyanaconda/storage/partitioning.py | 7 ++++--- 4 files changed, 28 insertions(+), 8 deletions(-)
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index 8fa3f8b..34040fe 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -934,6 +934,9 @@ class BootLoader(object): # def write(self): """ Write the bootloader configuration and install the bootloader. """ + if self.skip_bootloader: + return + if self.update_only: self.update() return @@ -1532,6 +1535,9 @@ class GRUB2(GRUB):
def write(self): """ Write the bootloader configuration and install the bootloader. """ + if self.skip_bootloader: + return + if self.update_only: self.update() return @@ -1631,6 +1637,9 @@ class EFIGRUB(GRUB2): # def write(self): """ Write the bootloader configuration and install the bootloader. """ + if self.skip_bootloader: + return + if self.update_only: self.update() return @@ -2157,10 +2166,11 @@ def writeBootLoader(storage, payload, instClass): """ from pyanaconda.errors import errorHandler, ERROR_RAISE
- stage1_device = storage.bootloader.stage1_device - log.info("bootloader stage1 target device is %s" % stage1_device.name) - stage2_device = storage.bootloader.stage2_device - log.info("bootloader stage2 target device is %s" % stage2_device.name) + if not storage.bootloader.skip_bootloader: + stage1_device = storage.bootloader.stage1_device + log.info("bootloader stage1 target device is %s" % stage1_device.name) + stage2_device = storage.bootloader.stage2_device + log.info("bootloader stage2 target device is %s" % stage2_device.name)
# get a list of installed kernel packages kernel_versions = payload.kernelVersionList @@ -2187,6 +2197,10 @@ def writeBootLoader(storage, payload, instClass): # write out /etc/sysconfig/kernel writeSysconfigKernel(storage, version)
+ if storage.bootloader.skip_bootloader: + log.info("skipping bootloader install per user request") + return + # now add an image for each of the other kernels for version in kernel_versions: label = "%s-%s" % (base_label, version) diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index 2600c65..7823354 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -281,6 +281,7 @@ class Bootloader(commands.bootloader.F18_Bootloader): storage.bootloader.update_only = True
if not location: + storage.bootloader.skip_bootloader = True return
if self.appendLine: diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 5c00143..e71be35 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -1510,7 +1510,7 @@ class Storage(object): warnings.append(_("Installing on a FireWire device. This may " "or may not produce a working system."))
- if self.data and self.data.bootloader.location is not None: + if self.bootloader and not self.bootloader.skip_bootloader: stage1 = self.bootloader.stage1_device if not stage1: errors.append(_("you have not created a bootloader stage1 " @@ -1671,6 +1671,10 @@ class Storage(object): log.warning("either ksdata or bootloader data missing") return
+ if self.bootloader.skip_bootloader: + log.info("user specified that bootloader install be skipped") + return + self.bootloader.stage1_disk = self.devicetree.resolveDevice(self.data.bootloader.bootDrive) self.bootloader.stage2_device = self.bootDevice try: diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index 9d70bd2..8e784ee 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -133,7 +133,7 @@ def _schedulePartitions(storage, disks): continue
elif request.fstype in ("prepboot", "efi", "hfs+") and \ - stage1_device: + (storage.bootloader.skip_bootloader or stage1_device): # there should never be a need for more than one of these # partitions, so skip them. log.info("skipping unneeded stage1 %s request" % request.fstype) @@ -152,8 +152,9 @@ def _schedulePartitions(storage, disks): any([p.format.type == "biosboot" for p in storage.partitions if p.disk == stage1_device])) - if not (stage1_device and stage1_device.isDisk and - is_gpt and not has_bios_boot): + if (storage.bootloader.skip_bootloader or + not (stage1_device and stage1_device.isDisk and + is_gpt and not has_bios_boot)): # there should never be a need for more than one of these # partitions, so skip them. log.info("skipping unneeded stage1 %s request" % request.fstype)
Also make it a bit easier to see what's going on by performing removals from the container's disk set separately from toggling member encryption. --- pyanaconda/storage/__init__.py | 35 ++++++++++++++++++++--------------- 1 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index e71be35..79fd36b 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -1857,17 +1857,30 @@ class Storage(object):
# prepare already-defined member partitions for reallocation for member in members[:]: + if any([d in remove_disks for d in member.disks]): + if isinstance(member, LUKSDevice): + container.removeMember(member) + self.destroyDevice(member) + members.remove(member) + member = member.slave + else: + container.removeMember(member) + members.remove(member) + + self.destroyDevice(member) + continue + if isinstance(member, LUKSDevice): - if member.slave.disk in remove_disks or not factory.encrypted: + if not factory.encrypted: + # encryption was toggled for the member devices container.removeMember(member) self.destroyDevice(member) members.remove(member) - if not factory.encrypted: - # encryption was toggled for the member devices - self.formatDevice(member.slave, - getFormat(factory.member_format)) - members.append(member.slave) - container.addMember(member.slave) + + self.formatDevice(member.slave, + getFormat(factory.member_format)) + members.append(member.slave) + container.addMember(member.slave)
member = member.slave elif factory.encrypted: @@ -1882,14 +1895,6 @@ class Storage(object): members.append(member) container.addMember(member)
- if member.disk in remove_disks: - if member in container.parents: - container.removeMember(member) - members.remove(member) - - self.destroyDevice(member) - continue - member.req_base_size = base_size member.req_size = member.req_base_size member.req_grow = True
I hit a bug triggered by repeatedly toggling encryption for a vg while also doing some size changes that revealed a problem in the current apply/propagate_actions approach. There's just too much that can happen to try to duplicate/cover it all.
Just replacing the main Storage instance isn't an option since each of the ui spokes holds a reference to it. However, all of the data is really in just a few data attributes of Storage and DeviceTree. --- pyanaconda/ui/gui/spokes/custom.py | 195 +++++------------------------------ pyanaconda/ui/gui/spokes/storage.py | 2 + 2 files changed, 28 insertions(+), 169 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index f33d34e..7a33352 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -21,6 +21,7 @@ #
# TODO: +# - empty size when adding a mountpoint is no longer using all available space # - Deleting an LV is not reflected in available space in the bottom left. # - this is only true for preexisting LVs # - Device descriptions, suggested sizes, etc. should be moved out into a support file. @@ -351,6 +352,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._current_selector = None self._when_create_text = "" self._devices = [] + self._media_disks = [] self._fs_types = [] # list of supported fstypes self._unused_devices = None # None indicates uninitialized self._free_space = Size(bytes=0) @@ -358,179 +360,33 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
self._initialized = False
- def _propagate_actions(self, actions): - """ Register actions from the UI with the main Storage instance. """ - for ui_action in actions: - ui_device = ui_action.device - if isinstance(ui_device, LUKSDevice) and ui_device.exists: - # LUKS device that was unlocked during this spoke visit - # The ids won't match in this case because we instantiated the - # LUKSDevice both in the spoke's devicetree and in the main one. - device = self.storage.devicetree.getDeviceByName(ui_device.name) - else: - device = self.storage.devicetree.getDeviceByID(ui_device.id) - - if device is None: - # This device does not exist in the main devicetree. - # - # That means it was created in the ui. If it is still present in - # the ui device list, schedule a create action for it. If not, - # just ignore it. - if not ui_action.isCreate or ui_device not in self._devices: - # this is a device that was created and then destroyed here - continue - - args = [device] - if ui_action.isResize: - args.append(ui_device.targetSize) - - if ui_action.isCreate and ui_action.isFormat: - if ui_device.format.type == "disklabel": - # A DiskLabel instance will have partitions already in it. - args.append(getFormat("disklabel", - device=ui_device.path, - labelType=ui_device.format.labelType)) - else: - args.append(ui_device.format) - - if ui_action.isCreate and ui_action.isDevice: - # We're going to just move the already-defined device into the - # main devicetree, but first we need to replace its parents - # with the corresponding devices from the main devicetree - # instead of the ones from our local devicetree. - parents = [self.storage.devicetree.getDeviceByID(p.id) - for p in ui_device.parents] - - if hasattr(ui_device, "partedPartition"): - # This cleans up any references to parted.Disk instances - # that only exist in our local devicetree. - ui_device.partedPartition = None - - req_disks = [self.storage.devicetree.getDeviceByID(p.id) - for p in ui_device.req_disks] - ui_device.req_disks = req_disks - - # If we somehow ended up with a different number of parents - # go ahead and take a dump on the floor. - assert(len(parents) == len(ui_device.parents)) - ui_device.parents = parents - args = [ui_device] - - log.debug("duplicating action '%s'" % ui_action) - action = ui_action.__class__(*args) - self.storage.devicetree.registerAction(action) - def apply(self): self.clear_errors() - ui_devicetree = self.__storage.devicetree - - log.debug("converting custom spoke changes into actions") - for action in ui_devicetree.findActions(): - log.debug("%s" % action) - - # Find any LUKS devices that have been unlocked in this visit to the - # custom spoke and find their descendant devices before we do anything - # else. - ui_luks_devices = [d for d in self._devices - if isinstance(d, LUKSDevice) and - d.exists and not - self.storage.devicetree.getDeviceByID(d.id)] - for ui_luks_device in ui_luks_devices: - if ui_luks_device not in self._devices: - # since removed - continue - - ui_slave = ui_luks_device.slave - slave = self.storage.devicetree.getDeviceByID(ui_slave.id) - slave.format.passphrase = ui_slave.format._LUKS__passphrase - luks_device = LUKSDevice(slave.format.mapName, - parents=[slave], - exists=True) - luks_device.setup() - self.storage.savePassphrase(slave) - self.storage.devicetree._addDevice(luks_device) - - # XXX What if the user has changed storage config in the shell? - if ui_luks_devices: - self.storage.devicetree.populate() - - # schedule actions for device removals, resizes - actions = ui_devicetree.findActions(type="destroy") - partition_destroys = [a for a in actions - if a.device.type == "partition"] - actions.extend(ui_devicetree.findActions(type="resize")) - self._propagate_actions(actions) - - # register all disklabel create actions - actions = ui_devicetree.findActions(type="create", object="format") - disklabel_creates = [a for a in actions - if a.device.format.type == "disklabel"] - self._propagate_actions(disklabel_creates) - - # register partition create actions, including formatting - actions = ui_devicetree.findActions(type="create") - partition_creates = [a for a in actions if a.device.type == "partition"] - self._propagate_actions(partition_creates) - - # catch changed size of partitions defined prior to this visit - for ui_device in self.__storage.partitions: - device = self.storage.devicetree.getDeviceByID(ui_device.id) - if device and not device.exists and ui_device.size != device.size: - device.req_base_size = ui_device.req_base_size - device.req_size = ui_device.req_size - - if partition_creates or partition_destroys: - try: - doPartitioning(self.storage) - except Exception as e: - # TODO: error handling - raise - - # catch changed size of non-partition devices defined prior to this - # visit - for ui_device in self.__storage.devices: - if ui_device in self.__storage.partitions: - # did partitions before doPartitioning - continue
- device = self.storage.devicetree.getDeviceByID(ui_device.id) - if device and not device.exists and ui_device.size != device.size: - device._size = ui_device._size - - # register all other create actions - already_handled = disklabel_creates + partition_creates - actions = [a for a in ui_devicetree.findActions(type="create") - if a not in already_handled] - self._propagate_actions(actions) - - # check for changes that do not use actions - for ui_device in self.__storage.devices: - device = self.storage.devicetree.getDeviceByID(ui_device.id) - if not device: - continue - - if device.format.mountable and \ - ui_device.format.mountpoint != device.format.mountpoint: - device.format.mountpoint = ui_device.format.mountpoint - - # we can only label new formats as of now - if hasattr(device.format, "label") and \ - not device.format.exists and \ - ui_device.format.label != device.format.label: - device.format.label = ui_device.format.label - - # if this device was removed from the new installation it should be - # completely reset to its initial state - if ui_device.format == ui_device.originalFormat and \ - ui_device.size == ui_device.currentSize: - self.storage.resetDevice(device) - - # apply global passphrase to all new encrypted devices + # unhide any removable install media we hid prior to partitioning + with ui_storage_logger(): + for disk in reversed(self._media_disks): + self.__storage.devicetree.unhide(disk) + + # We can't overwrite the main Storage instance because all the other + # spokes have references to it that would get invalidated, but we can + # achieve the same effect by updating/replacing a few key attributes. + self.storage.devicetree._devices = self.__storage.devicetree._devices + self.storage.devicetree._actions = self.__storage.devicetree._actions + self.storage.devicetree._hidden = self.__storage.devicetree._hidden + self.storage.devicetree.names = self.__storage.devicetree.names + self.storage.roots = self.__storage.roots + + # update the global passphrase self.data.autopart.passphrase = self.passphrase + + # make sure any device/passphrase pairs we've obtained are remebered for device in self.storage.devices: - if device.format.type == "luks" and not device.format.exists: - log.debug("using global passphrase for %s" % device.name) - device.format.passphrase = self.data.autopart.passphrase + if device.format.type == "luks" and not device.exists: + if not device.format.hasKey: + device.format.passphrase = self.passphrase + + self.storage.savePassphrase(device)
# set up bootloader and check the configuration self.storage.setUpBootLoader() @@ -678,11 +534,12 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
def _reset_storage(self): self.__storage = self.storage.copy() - self.__storage.devicetree._actions = [] # keep things simple + self._media_disks = []
# hide removable disks containing install media for disk in self.__storage.disks: if disk.removable and disk.protected: + self._media_disks.append(disk) self.__storage.devicetree.hide(disk)
self._devices = self.__storage.devices diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py index 9931e36..bf8067b 100644 --- a/pyanaconda/ui/gui/spokes/storage.py +++ b/pyanaconda/ui/gui/spokes/storage.py @@ -464,6 +464,8 @@ class StorageSpoke(NormalSpoke, StorageChecker): self._update_summary()
def refresh(self): + self.disks = getDisks(self.storage.devicetree) + # synchronize our local data store with the global ksdata disk_names = [d.name for d in self.disks] # don't put disks with hidden formats in selected_disks
This doesn't come up much because someone's always activating the md arrays before anaconda gets run. --- pyanaconda/storage/devicelibs/mdraid.py | 11 +++++------ pyanaconda/storage/devicetree.py | 18 +++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-)
diff --git a/pyanaconda/storage/devicelibs/mdraid.py b/pyanaconda/storage/devicelibs/mdraid.py index ee6a465..13d7832 100644 --- a/pyanaconda/storage/devicelibs/mdraid.py +++ b/pyanaconda/storage/devicelibs/mdraid.py @@ -240,12 +240,11 @@ def mdexamine(device): stderr="/dev/tty5").split()
info = {} - if vars: - try: - info["device"] = vars[1] - vars = vars[2:] - except IndexError: - return {} + if len(vars) > 1 and vars[1].startswith("/dev/md"): + info["device"] = vars[1] + vars = vars[2:] + elif len(vars) > 1: + vars = vars[1:]
for var in vars: (name, equals, value) = var.partition("=") diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index be14ba2..6c531ff 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -1418,17 +1418,13 @@ class DeviceTree(object):
if not md_name: md_path = md_info.get("device", "") - md_name = devicePathToName(md_info.get("device", "")) - if md_name: - try: - # md_name can be md<minor>, md/<minor>, or md/<name> - if md_name.startswith("md/"): - md_name = md_name[3:] - else: - md_name = md_name[2:] - except (IndexError, ValueError): - md_name = None - else: + if md_path: + md_name = devicePathToName(md_path) + if re.match(r'md\d+$', md_name): + # md0 -> 0 + md_name = md_name[2:] + + if md_name: array = self.getDeviceByName(md_name) if array and array.uuid != md_uuid: md_name = None
If the storage spoke was completed prior to entering the custom spoke and that visit introduced errors, the storage spoke status wasn't getting updated accordingly, allowing users with faulty configurations to proceed with a doomed install. --- pyanaconda/ui/gui/spokes/custom.py | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 7a33352..ed1a0d1 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -70,6 +70,7 @@ from pyanaconda.storage.devicelibs import mdraid from pyanaconda.storage.devices import LUKSDevice
from pyanaconda.ui.gui import GUIObject +from pyanaconda.ui.gui import communication from pyanaconda.ui.gui.spokes import NormalSpoke from pyanaconda.ui.gui.spokes.storage import StorageChecker from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog @@ -390,7 +391,10 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# set up bootloader and check the configuration self.storage.setUpBootLoader() + + StorageChecker.errors = [] StorageChecker.run(self) + communication.send_ready("StorageSpoke", justUpdate=True)
@property def indirect(self):
The old method only worked once. After that, it was trying to cancel create actions that had already been canceled. --- pyanaconda/ui/gui/spokes/storage.py | 8 +++++--- 1 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py index bf8067b..3c0059b 100644 --- a/pyanaconda/ui/gui/spokes/storage.py +++ b/pyanaconda/ui/gui/spokes/storage.py @@ -631,9 +631,11 @@ class StorageSpoke(NormalSpoke, StorageChecker): # refreshed. if self._previous_autopart: self._previous_autopart = False - actions = self.storage.devicetree.findActions(type="create") - for action in reversed(actions): - self.storage.devicetree.cancelAction(action) + for partition in self.storage.partitions[:]: + # check if it's been removed in a previous iteration + if not partition.exists and \ + partition in self.storage.partitions: + self.storage.recursiveRemove(partition)
# hide/unhide disks as requested for disk in self.disks:
--- pyanaconda/storage/__init__.py | 15 +++++++++++++-- 1 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 79fd36b..f6f09a1 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -39,6 +39,7 @@ from pyanaconda.flags import flags from pyanaconda import tsort from pyanaconda.errors import * from pyanaconda.bootloader import BootLoaderError +from pyanaconda.anaconda_log import log_method_call
from errors import * from devices import * @@ -1820,6 +1821,11 @@ class Storage(object): def setContainerMembers(self, container, factory, members=None, device=None): """ Set up and return the container's member partitions. """ + log_members = [] + if members: + log_members = [str(m) for m in members] + log_method_call(self, container=container, factory=factory, + members=log_members, device=device) if factory.member_list is not None: # short-circuit the logic below for partitions return factory.member_list @@ -1838,8 +1844,12 @@ class Storage(object): if container: members = container.parents[:]
+ # The basis for whether we are modifying a member set versus creating + # one must be the member list, as container will be None when modifying + # the member set of an md array. + # XXX how can we detect/handle failure to use one or more of the disks? - if container and device: + if members and device: # See if we need to add/remove any disks, but only if we are # adjusting a device. When adding a new device to a container we do # not want to modify the container's disk set. @@ -1847,7 +1857,7 @@ class Storage(object):
add_disks = [d for d in factory.disks if d not in _disks] remove_disks = [d for d in _disks if d not in factory.disks] - elif not container: + elif not members: # new container, so use the factory's disk set add_disks = factory.disks
@@ -2040,6 +2050,7 @@ class Storage(object): we raise ErrorRecoveryFailure to alert the caller that things will likely be in an inconsistent state. """ + log_method_call(self, device_type, size, **kwargs) mountpoint = kwargs.get("mountpoint") fstype = kwargs.get("fstype") label = kwargs.get("label")
--- pyanaconda/storage/devices.py | 1 - pyanaconda/storage/devicetree.py | 4 ++++ pyanaconda/ui/gui/spokes/custom.py | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py index f0ebc64..e465c2d 100644 --- a/pyanaconda/storage/devices.py +++ b/pyanaconda/storage/devices.py @@ -4113,5 +4113,4 @@ class BTRFSSubVolumeDevice(BTRFSDevice): if not mountpoint: raise RuntimeError("btrfs subvol destroy requires mounted volume") btrfs.delete_subvolume(mountpoint, self.name) - self.volume._removeSubVolume(self.name) self.volume._undo_temp_mount() diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index 6c531ff..3fad2c7 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -392,6 +392,10 @@ class DeviceTree(object): if isinstance(device, PartitionDevice) and \ device.disk == dev.disk: device.updateName() + elif hasattr(dev, "vg"): + dev.vg._removeLogVol(dev) + elif hasattr(dev, "volume"): + dev.volume._removeSubVolume(dev.name)
self._devices.remove(dev) if dev.name in self.names: diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index ed1a0d1..d4cc35e 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -1649,12 +1649,10 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # should this be in DeviceTree._removeDevice? container = None if hasattr(device, "vg"): - device.vg._removeLogVol(device) container = device.vg device_type = DEVICE_TYPE_LVM raid_level = None elif hasattr(device, "volume"): - device.volume._removeSubVolume(device.name) container = device.volume device_type = DEVICE_TYPE_BTRFS raid_level = container.dataLevel
It's not necessarily intuitive, but your only option is to delete and re-create. --- pyanaconda/ui/gui/spokes/custom.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index d4cc35e..29206d3 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -1391,7 +1391,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): sizeSpinner.set_tooltip_text(_("This file system may not be resized."))
reformatCheckbox.set_active(not device.format.exists) - reformatCheckbox.set_sensitive(use_dev.exists) + reformatCheckbox.set_sensitive(use_dev.exists and + not use_dev.type.startswith("btrfs"))
encryptCheckbox.set_active(device.encrypted) encryptCheckbox.set_sensitive(reformatCheckbox.get_active())
On Fri, Nov 09, 2012 at 03:09:10PM -0600, David Lehman wrote:
This set fixes several bugs, and then several more that I hit while testing the fixes. I spent all day today testing these, doing as many combinations as I could think of of custom and automatic partitioning, encrypted and not, btrfs, lvm, md, &c. I'm getting pretty bored with installing fedora all the time.
These all look ok.
anaconda-patches@lists.fedorahosted.org