This makes the "edit VG" stuff into "edit container" where container could be either an LVM VG or a BTRFS volume. It moves container-wide settings like encryption and raid into the container dialog so users can have some sense of what they're setting up (encrypt a single lv versus encrypt the entire vg).
This also adds support for creating lvm-on-md, which the user can get by setting a raid level other than None for their VG.
Some things that probably need to be done, but not necessarily now: 1. make encryption of an lv and its vg mutually exclusive 2. ditto for raid (don't allow a striped lv in a vg that has md pvs)
I spent the better part of two weeks testing this, but that should provide little comfort given the number of paths through custom storage.
--- blivet/devicefactory.py | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index bef5bb5..9ed2569 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -77,6 +77,8 @@ def get_raid_level(device): raid_level = use_dev.dataLevel or "single" elif hasattr(use_dev, "volume"): raid_level = use_dev.volume.dataLevel or "single" + elif hasattr(use_dev, "lvs") and len(use_dev.parents) == 1: + raid_level = get_raid_level(use_dev.parents[0])
return raid_level
This goes along with the changes to anaconda's custom spoke to expand its container management and improve separation between device-level settings and container-level settings. --- blivet/devicefactory.py | 52 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 16 deletions(-)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 9ed2569..5685ad0 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -209,6 +209,7 @@ class DeviceFactory(object): def __init__(self, storage, size, disks, fstype=None, mountpoint=None, label=None, raid_level=None, encrypted=False, container_encrypted=False, container_name=None, + container_raid_level=None, name=None, device=None): """ Create a new DeviceFactory instance.
@@ -225,6 +226,8 @@ class DeviceFactory(object): label filesystem label text
raid_level raid level string, eg: "raid1" + container_raid_level + encrypted whether to encrypt (boolean) container_encrypted whether to encrypt the entire container
@@ -246,6 +249,8 @@ class DeviceFactory(object): self.label = label
self.raid_level = raid_level + self.container_raid_level = container_raid_level + self.encrypted = encrypted self.container_encrypted = container_encrypted
@@ -1155,19 +1160,34 @@ class LVMOnMDFactory(LVMFactory): """ child_factory_class = MDFactory
- def __init__(self, *args, **kwargs): - self.md_level = kwargs.pop("raid_level", None) or "raid1" - super(LVMOnMDFactory, self).__init__(*args, **kwargs) - def _get_child_factory_kwargs(self): kwargs = super(LVMOnMDFactory, self)._get_child_factory_kwargs() - kwargs["raid_level"] = self.md_level - kwargs["name"] = self.storage.suggestDeviceName(prefix="pv") - if self.container: + kwargs["raid_level"] = self.container_raid_level + if self.container and self.container.parents: kwargs["device"] = self.container.parents[0] - kwargs["name"] = self.container.parents[0].name + else: + kwargs["name"] = self.storage.suggestDeviceName(prefix="pv") + return kwargs
+ def _configure(self): + # If there's already a VG associated with this LV that doesn't have MD + # PVs we need to remove the partition PVs. + self._set_container() + if self.container: + for member in self.container.parents[:]: + use_dev = member + if isinstance(member, LUKSDevice): + use_dev = member.slave + + if use_dev.type != "mdarray": + self.container.removeMember(member) + self.storage.destroyDevice(member) + if member != use_dev: + self.storage.destroyDevice(use_dev) + + super(LVMOnMDFactory, self)._configure() + class BTRFSFactory(DeviceFactory): """ BTRFS subvolume """ child_factory_class = PartitionSetFactory @@ -1180,8 +1200,8 @@ class BTRFSFactory(DeviceFactory): log.info("overriding encryption setting for btrfs factory") self.encrypted = False
- self.raid_level = self.raid_level or "single" - if self.raid_level == "single": + self.container_raid_level = self.container_raid_level or "single" + if self.container_raid_level == "single": self.size_set_class = TotalSizeSet else: self.size_set_class = SameSizeSet @@ -1205,9 +1225,9 @@ class BTRFSFactory(DeviceFactory):
def _get_device_space(self): # until we get/need something better - if self.raid_level in ("single", "raid0"): + if self.container_raid_level in ("single", "raid0"): return self.size - elif self.raid_level in ("raid1", "raid10"): + elif self.container_raid_level in ("raid1", "raid10"): return self.size * len(self._get_member_devices())
@property @@ -1221,7 +1241,7 @@ class BTRFSFactory(DeviceFactory): """ Create the container device required by this factory device. """ parents = self._get_parent_devices() self.container = self._get_new_container(name=self.container_name, - dataLevel=self.raid_level, + dataLevel=self.container_raid_level, parents=parents) self.storage.createDevice(self.container)
@@ -1244,7 +1264,7 @@ class BTRFSFactory(DeviceFactory): # make sure the member count is adequate for the new level
# set the new level - self.container.dataLevel = self.raid_level + self.container.dataLevel = self.container_raid_level
def _get_child_factory_kwargs(self): kwargs = super(BTRFSFactory, self)._get_child_factory_kwargs() @@ -1253,7 +1273,7 @@ class BTRFSFactory(DeviceFactory):
def _get_new_device(self, *args, **kwargs): """ Create and return the factory device as a StorageDevice. """ - kwargs["dataLevel"] = self.raid_level - kwargs["metaDataLevel"] = self.raid_level + kwargs["dataLevel"] = self.container_raid_level + kwargs["metaDataLevel"] = self.container_raid_level kwargs["subvol"] = True return self.storage.newBTRFS(*args, **kwargs)
This allows us to access it from anywhere and also to stop passing it around explicitly. --- blivet/devicefactory.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 5685ad0..50a06d6 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -266,6 +266,7 @@ class DeviceFactory(object): self.mountpoint = None
self.child_factory = None + self.parent_factory = None
# used for error recovery self.__devices = [] @@ -641,23 +642,22 @@ class DeviceFactory(object): factory = self.child_factory_class(*self._get_child_factory_args(), **self._get_child_factory_kwargs()) self.child_factory = factory + factory.parent_factory = self
- def configure(self, parent_factory=None): + def configure(self): """ Configure the factory's device(s).
Keyword arguments:
- parent_factory -- a factory that created/called this factory - An example of the parent_factory is the LVMOnMDFactory creating and then using an MDFactory to manage the volume group's single MD PV.
Another example is the MDFactory creating and then using a PartitionSetFactory to manage the set of member partitions. """ - log_method_call(self, parent_factory=parent_factory) + log_method_call(self, parent_factory=self.parent_factory)
- if parent_factory is None: + if self.parent_factory is None: # only do the backup/restore error handling in the top-level factory self._save_devicetree()
@@ -665,7 +665,7 @@ class DeviceFactory(object): self._configure() except Exception as e: log.error("failed to configure device factory: %s" % e) - if parent_factory is None: + if self.parent_factory is None: # only do the backup/restore error handling at the top-level self._revert_devicetree()
@@ -674,7 +674,7 @@ class DeviceFactory(object):
raise(e)
- def _configure(self, parent_factory=None): + def _configure(self): self._set_container() self._handle_no_size() self._set_up_child_factory() @@ -683,7 +683,7 @@ class DeviceFactory(object): # for type-specific container devices. In the LVM example, this will # configure the PVs. if self.child_factory: - self.child_factory.configure(parent_factory=self) + self.child_factory.configure()
# Configure any type-specific container device. The obvious example of # this is the LVMFactory, which will configure its VG in this step. @@ -814,12 +814,12 @@ class PartitionSetFactory(PartitionFactory): def devices(self): return self._devices
- def configure(self, parent_factory=None): + def configure(self): """ Configure the factory's device set.
This factory class will always have a parent factory. """ - log_method_call(self, parent_factory=parent_factory) + log_method_call(self, parent_factory=self.parent_factory)
# list of disks to add/remove member devices to/from add_disks = [] @@ -834,8 +834,8 @@ class PartitionSetFactory(PartitionFactory):
# Grab the starting member list from the parent factory. members = self._devices - container = parent_factory.container - log.debug("parent factory container: %s" % parent_factory.container) + container = self.parent_factory.container + log.debug("parent factory container: %s" % self.parent_factory.container) if container: # update our device list from the parent factory's container members members = container.parents[:] @@ -847,7 +847,7 @@ class PartitionSetFactory(PartitionFactory): ## Determine the target disk set. ## # XXX how can we detect/handle failure to use one or more of the disks? - if parent_factory.device: + if self.parent_factory.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. @@ -860,7 +860,7 @@ class PartitionSetFactory(PartitionFactory): add_disks = self.disks
# drop any new disks that don't have free space - min_free = min(500, parent_factory.size) + min_free = min(500, self.parent_factory.size) add_disks = [d for d in add_disks if d.partitioned and d.format.free >= min_free]
@@ -965,14 +965,14 @@ class PartitionSetFactory(PartitionFactory): ## ## Determine target container size. ## - total_space = parent_factory._get_total_space() + total_space = self.parent_factory._get_total_space()
## ## Set up SizeSet to manage growth of member partitions. ## log.debug("adding a %s with size %d" - % (parent_factory.size_set_class.__name__, total_space)) - size_set = parent_factory.size_set_class(members, total_space) + % (self.parent_factory.size_set_class.__name__, total_space)) + size_set = self.parent_factory.size_set_class(members, total_space) self.storage.size_sets.append(size_set) for member in members[:]: if isinstance(member, LUKSDevice):
It is possible the user specified an adequate number of disks but some of those disks were unusable because of a lack of free space. --- blivet/devicefactory.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 50a06d6..5f3604a 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -25,6 +25,7 @@ from errors import * from devices import LUKSDevice from formats import getFormat from devicelibs.mdraid import get_member_space +from devicelibs.mdraid import get_raid_min_members from devicelibs.mdraid import raidLevelString from devicelibs.mdraid import raidLevel from devicelibs.lvm import get_pv_space @@ -685,6 +686,19 @@ class DeviceFactory(object): if self.child_factory: self.child_factory.configure()
+ # Make sure that there are enough disks involved for any specified + # device or container raid level. + for level_attr in ["raid_level", "container_raid_level"]: + level = getattr(self, level_attr, None) + if level in [None, "single"]: + continue + + md_level = raidLevel(level) + min_disks = get_raid_min_members(md_level) + disks = set(d for m in self._get_member_devices() for d in m.disks) + if len(disks) < min_disks: + raise DeviceFactoryError("Not enough disks for %s" % level) + # Configure any type-specific container device. The obvious example of # this is the LVMFactory, which will configure its VG in this step. if self.container:
--- blivet/__init__.py | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/blivet/__init__.py b/blivet/__init__.py index 99faefa..4f157aa 100644 --- a/blivet/__init__.py +++ b/blivet/__init__.py @@ -1871,6 +1871,10 @@ class Blivet(object): device_type == devicefactory.DEVICE_TYPE_BTRFS: device_type = devicefactory.DEVICE_TYPE_PARTITION
+ if device_type == devicefactory.DEVICE_TYPE_LVM and \ + kwargs.get("container_raid_level"): + device_type = devicefactory.DEVICE_TYPE_LVM_ON_MD + factory = devicefactory.get_device_factory(self, device_type, size, **kwargs)
--- blivet/devicefactory.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 5f3604a..d759aa0 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -381,11 +381,10 @@ class DeviceFactory(object): elif hasattr(device, "volume"): container = device.volume elif name: - container = self.storage.devicetree.getDeviceByName(name) - if container and container not in self.container_list: - log.debug("specified container name %s is wrong type (%s)" - % (name, container.type)) - container = None + for c in self.storage.devices: + if c.name == name and c in self.container_list: + container = c + break else: containers = [c for c in self.container_list if allow_existing or not c.exists]
Otherwise you are trying to remove a non-leaf device. --- blivet/devicefactory.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index d759aa0..60ef145 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -594,13 +594,20 @@ class DeviceFactory(object):
def _set_encryption(self): # toggle encryption of the leaf device as needed + parent_container = getattr(self.parent_factory, "container", None) if isinstance(self.device, LUKSDevice) and not self.encrypted: raw_device = self.raw_device leaf_format = self.device.format + if parent_container: + parent_container.removeMember(self.device) self.storage.destroyDevice(self.device) self.storage.formatDevice(self.raw_device, leaf_format) self.device = raw_device + if parent_container: + parent_container.addMember(self.device) elif self.encrypted and not isinstance(self.device, LUKSDevice): + if parent_container: + parent_container.removeMember(self.device) leaf_format = self.device.format self.storage.formatDevice(self.device, getFormat("luks")) luks_device = LUKSDevice("luks-%s" % self.device.name, @@ -608,6 +615,8 @@ class DeviceFactory(object): parents=self.device) self.storage.createDevice(luks_device) self.device = luks_device + if parent_container: + parent_container.addMember(self.device)
def _set_name(self): if self.device_name is None: @@ -903,11 +912,11 @@ class PartitionSetFactory(PartitionFactory): for member in members[:]: member_encrypted = isinstance(member, LUKSDevice) if member_encrypted and not self.encrypted: - self.storage.destroyDevice(member) - members.remove(member) if container: container.removeMember(member)
+ self.storage.destroyDevice(member) + members.remove(member) self.storage.formatDevice(member.slave, getFormat(self.fstype)) members.append(member.slave)
--- blivet/devicefactory.py | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py index 60ef145..88336d1 100644 --- a/blivet/devicefactory.py +++ b/blivet/devicefactory.py @@ -29,6 +29,7 @@ from devicelibs.mdraid import get_raid_min_members from devicelibs.mdraid import raidLevelString from devicelibs.mdraid import raidLevel from devicelibs.lvm import get_pv_space +from devicelibs.lvm import LVM_PE_SIZE from .partitioning import SameSizeSet from .partitioning import TotalSizeSet from .partitioning import doPartitioning @@ -1182,6 +1183,12 @@ class LVMOnMDFactory(LVMFactory): """ child_factory_class = MDFactory
+ def _get_total_space(self): + base = super(LVMOnMDFactory, self)._get_total_space() + # add one extent per disk to account for mysterious space requirements + base += LVM_PE_SIZE * len(self.disks) + return base + def _get_child_factory_kwargs(self): kwargs = super(LVMOnMDFactory, self)._get_child_factory_kwargs() kwargs["raid_level"] = self.container_raid_level
Add ability to manage btrfs volumes in addition to lvm vgs.
Move container-wide raid and encryption settings into the edit container dialog. --- pyanaconda/ui/gui/spokes/custom.glade | 863 +++++++++++++++++++--------------- pyanaconda/ui/gui/spokes/custom.py | 436 ++++++++++++----- 2 files changed, 801 insertions(+), 498 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/custom.glade b/pyanaconda/ui/gui/spokes/custom.glade index ea6bcd3..f45a482 100644 --- a/pyanaconda/ui/gui/spokes/custom.glade +++ b/pyanaconda/ui/gui/spokes/custom.glade @@ -2,76 +2,6 @@ <interface> <!-- interface-requires gtk+ 3.0 --> <!-- interface-requires AnacondaWidgets 1.0 --> - <object class="GtkDialog" id="help_dialog"> - <property name="can_focus">False</property> - <property name="border_width">5</property> - <property name="title" translatable="yes">Help</property> - <property name="modal">True</property> - <property name="window_position">center-on-parent</property> - <property name="type_hint">dialog</property> - <property name="width_request">600</property> - <property name="height_request">400</property> - <signal name="close" handler="on_close" swapped="no"/> - <child internal-child="vbox"> - <object class="GtkBox" id="dialog-vbox6"> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <child internal-child="action_area"> - <object class="GtkButtonBox" id="dialog-action_area6"> - <property name="can_focus">False</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="help_close_button"> - <property name="label">gtk-close</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_stock">True</property> - <signal name="clicked" handler="on_close" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack_type">end</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow3"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkTextView" id="help_text_view"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="editable">False</property> - <property name="wrap_mode">word</property> - <property name="cursor_visible">False</property> - <property name="buffer">help_text_buffer</property> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <action-widgets> - <action-widget response="0">help_close_button</action-widget> - </action-widgets> - </object> <object class="GtkDialog" id="addDialog"> <property name="can_focus">False</property> <property name="border_width">5</property> @@ -395,101 +325,425 @@ use. Try something else?</property> <action-widget response="1">deleteConfirmButton</action-widget> </action-widgets> </object> - <object class="AnacondaSpokeWindow" id="customStorageWindow"> - <property name="startup_id">filler</property> + <object class="GtkDialog" id="container_dialog"> <property name="can_focus">False</property> - <property name="startup_id">filler</property> - <property name="window_name" translatable="yes">MANUAL PARTITIONING</property> - <property name="button_label" translatable="yes">_Done</property> - <signal name="info-bar-clicked" handler="on_info_bar_clicked" swapped="no"/> - <signal name="button-clicked" handler="on_back_clicked" swapped="no"/> - <child internal-child="main_box"> - <object class="GtkBox" id="AnacondaSpokeWindow-main_box1"> + <property name="border_width">5</property> + <property name="type_hint">dialog</property> + <property name="decorated">False</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox5"> <property name="can_focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> - <child internal-child="nav_box"> - <object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1"> - <child internal-child="nav_area"> - <object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1"> - <property name="can_focus">False</property> - <property name="margin_left">6</property> - <property name="margin_right">6</property> - <property name="margin_top">6</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area5"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="container_cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_cancel_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="container_save_button"> + <property name="label" translatable="yes">_Save</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_save_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> </child> - <child internal-child="alignment"> - <object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1"> + <child> + <object class="GtkBox" id="box10"> + <property name="visible">True</property> <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="xscale">1</property> - <property name="yscale">1</property> - <property name="yalign">0</property> - <property name="left_padding">24</property> - <property name="right_padding">24</property> - <child internal-child="action_area"> - <object class="GtkBox" id="customStorageWindow-actionArea"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="container_dialog_title_label"> + <property name="visible">True</property> <property name="can_focus">False</property> - <property name="margin_top">6</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CONFIGURE CONTAINER</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="container_dialog_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Please create a name for this container and select at least one disk below.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box11"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">container_name_entry</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="container_name_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + <accelerator key="n" signal="grab-focus" modifiers="GDK_MOD1_MASK"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="visible">True</property> + <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="vexpand">True</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> + <property name="shadow_type">in</property> <child> - <object class="GtkGrid" id="grid1"> + <object class="GtkViewport" id="viewport2"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="column_spacing">24</property> <child> - <object class="GtkScrolledWindow" id="partitionsScrolledWindow"> + <object class="GtkTreeView" id="container_disk_view"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="shadow_type">in</property> - <property name="min_content_width">325</property> + <property name="model">disk_store</property> + <property name="search_column">0</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection3"> + <property name="mode">multiple</property> + </object> + </child> <child> - <object class="GtkViewport" id="partitionsViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> + <object class="GtkTreeViewColumn" id="disk_column1"> + <property name="spacing">6</property> + <property name="title" translatable="yes">Disk</property> + <property name="clickable">True</property> <child> - <placeholder/> + <object class="GtkCellRendererText" id="disk_renderer1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> </child> </object> </child> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkNotebook" id="partitionsNotebook"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="show_tabs">False</property> <child> - <object class="GtkLabel" id="whenCreateLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">When you create mount points for your %s %s installation, you'll be able to view their details here.</property> - <property name="wrap">True</property> - </object> - </child> + <object class="GtkTreeViewColumn" id="capacity_column1"> + <property name="spacing">6</property> + <property name="title" translatable="yes">Capacity</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="capacity_renderer1"/> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="free_column1"> + <property name="spacing">6</property> + <property name="title" translatable="yes">Free</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="free_renderer1"/> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="id_column1"> + <property name="spacing">6</property> + <property name="title" translatable="yes">ID</property> + <property name="clickable">True</property> + <child> + <object class="GtkCellRendererText" id="id_renderer1"/> + <attributes> + <attribute name="text">3</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="containerRaidLevelLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">RAID Level:</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="containerRaidLevelCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="model">containerRaidStoreFiltered</property> + <child> + <object class="GtkCellRendererText" id="containerRaidLevelRenderer"/> + <attributes> + <attribute name="markup">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="containerEncryptedCheckbox"> + <property name="label" translatable="yes">Encrypt</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="containerErrorBox"> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage" id="containerErrorImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-dialog-warning</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="containerErrorLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Please enter a valid name.</property> + <property name="wrap">True</property> + <attributes> + <attribute name="font-desc" value="Cantarell 10"/> + <attribute name="scale" value="1"/> + <attribute name="foreground" value="#ffff00000000"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">container_cancel_button</action-widget> + <action-widget response="1">container_save_button</action-widget> + </action-widgets> + </object> + <object class="AnacondaSpokeWindow" id="customStorageWindow"> + <property name="startup_id">filler</property> + <property name="can_focus">False</property> + <property name="startup_id">filler</property> + <property name="window_name" translatable="yes">MANUAL PARTITIONING</property> + <signal name="info-bar-clicked" handler="on_info_bar_clicked" swapped="no"/> + <signal name="button-clicked" handler="on_back_clicked" swapped="no"/> + <child internal-child="main_box"> + <object class="GtkBox" id="AnacondaSpokeWindow-main_box1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child internal-child="nav_box"> + <object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1"> + <property name="can_focus">False</property> + <child internal-child="nav_area"> + <object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1"> + <property name="can_focus">False</property> + <property name="margin_left">6</property> + <property name="margin_right">6</property> + <property name="margin_top">6</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child internal-child="alignment"> + <object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1"> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="yalign">0</property> + <property name="xscale">0</property> + <property name="yscale">0.5</property> + <property name="left_padding">24</property> + <property name="right_padding">24</property> + <child internal-child="action_area"> + <object class="GtkBox" id="customStorageWindow-actionArea"> + <property name="can_focus">False</property> + <property name="margin_top">6</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="column_spacing">24</property> + <child> + <object class="GtkScrolledWindow" id="partitionsScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <property name="min_content_width">325</property> + <child> + <object class="GtkViewport" id="partitionsViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="partitionsNotebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <child> + <object class="GtkLabel" id="whenCreateLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">When you create mount points for your %s %s installation, you'll be able to view their details here.</property> + <property name="wrap">True</property> + </object> + </child> <child type="tab"> <placeholder/> </child> @@ -814,13 +1068,13 @@ use. Try something else?</property> </packing> </child> <child> - <object class="GtkLabel" id="volumeGroupLabel"> + <object class="GtkLabel" id="containerLabel"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="xalign">0</property> <property name="label" translatable="yes">_Volume Group:</property> <property name="use_underline">True</property> - <property name="mnemonic_widget">volumeGroupCombo</property> + <property name="mnemonic_widget">containerCombo</property> <attributes> <attribute name="weight" value="bold"/> </attributes> @@ -833,12 +1087,12 @@ use. Try something else?</property> </packing> </child> <child> - <object class="GtkComboBoxText" id="volumeGroupCombo"> + <object class="GtkComboBoxText" id="containerCombo"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="entry_text_column">0</property> <property name="id_column">1</property> - <signal name="changed" handler="on_vg_changed" swapped="no"/> + <signal name="changed" handler="on_container_changed" swapped="no"/> <signal name="changed" handler="on_value_changed" swapped="no"/> </object> <packing> @@ -849,13 +1103,13 @@ use. Try something else?</property> </packing> </child> <child> - <object class="GtkButton" id="modifyVGButton"> + <object class="GtkButton" id="modifyContainerButton"> <property name="label" translatable="yes">_Modify...</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="use_underline">True</property> - <signal name="clicked" handler="on_modify_vg_clicked" swapped="no"/> + <signal name="clicked" handler="on_modify_container_clicked" swapped="no"/> </object> <packing> <property name="left_attach">2</property> @@ -1307,7 +1561,6 @@ until you click on the main menu's 'Begin Installation' button.</property> </style> <child> <object class="GtkToolButton" id="addButton"> - <property name="use_action_appearance">False</property> <property name="width_request">42</property> <property name="height_request">36</property> <property name="visible">True</property> @@ -1342,7 +1595,6 @@ until you click on the main menu's 'Begin Installation' button.</property> </child> <child> <object class="GtkToolButton" id="configureButton"> - <property name="use_action_appearance">False</property> <property name="width_request">42</property> <property name="height_request">36</property> <property name="visible">True</property> @@ -1580,22 +1832,38 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> </object> - <object class="GtkDialog" id="vg_dialog"> + <object class="GtkListStore" id="disk_store"> + <columns> + <!-- column-name disk_col --> + <column type="gchararray"/> + <!-- column-name capacity_col --> + <column type="gchararray"/> + <!-- column-name free_col --> + <column type="gchararray"/> + <!-- column-name serial_col --> + <column type="gchararray"/> + <!-- column-name id_col --> + <column type="gint"/> + </columns> + </object> + <object class="GtkDialog" id="disks_dialog"> + <property name="width_request">400</property> + <property name="height_request">200</property> <property name="can_focus">False</property> <property name="border_width">5</property> <property name="type_hint">dialog</property> <property name="decorated">False</property> <child internal-child="vbox"> - <object class="GtkBox" id="dialog-vbox5"> + <object class="GtkBox" id="dialog-vbox4"> <property name="can_focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child internal-child="action_area"> - <object class="GtkButtonBox" id="dialog-action_area5"> + <object class="GtkButtonBox" id="dialog-action_area4"> <property name="can_focus">False</property> <property name="layout_style">end</property> <child> - <object class="GtkButton" id="vg_cancel_button"> + <object class="GtkButton" id="cancel_button"> <property name="label" translatable="yes">_Cancel</property> <property name="visible">True</property> <property name="can_focus">True</property> @@ -1610,13 +1878,13 @@ until you click on the main menu's 'Begin Installation' button.</property> </packing> </child> <child> - <object class="GtkButton" id="vg_save_button"> - <property name="label" translatable="yes">_Save</property> + <object class="GtkButton" id="select_button"> + <property name="label" translatable="yes">_Select</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="use_underline">True</property> - <signal name="clicked" handler="on_save_clicked" swapped="no"/> + <signal name="clicked" handler="on_select_clicked" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -1633,17 +1901,17 @@ until you click on the main menu's 'Begin Installation' button.</property> </packing> </child> <child> - <object class="GtkBox" id="box10"> + <object class="GtkBox" id="box1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> - <object class="GtkLabel" id="configure_vg_title_label"> + <object class="GtkLabel" id="disk_selection_title_label"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="xalign">0</property> - <property name="label" translatable="yes">CONFIGURE VOLUME GROUP</property> + <property name="label" translatable="yes">CONFIGURE MOUNT POINT</property> <attributes> <attribute name="weight" value="bold"/> </attributes> @@ -1655,10 +1923,10 @@ until you click on the main menu's 'Begin Installation' button.</property> </packing> </child> <child> - <object class="GtkLabel" id="label14"> + <object class="GtkLabel" id="label11"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">Please create a name for your volume group and select at least one disk below.</property> + <property name="label" translatable="yes">Select one or more disks this device may reside on.</property> <property name="wrap">True</property> </object> <packing> @@ -1668,73 +1936,33 @@ until you click on the main menu's 'Begin Installation' button.</property> </packing> </child> <child> - <object class="GtkBox" id="box11"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label15"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">_Name:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">vg_name_entry</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="vg_name_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">●</property> - <accelerator key="n" signal="grab-focus" modifiers="GDK_MOD1_MASK"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow2"> + <object class="GtkScrolledWindow" id="scrolledwindow1"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="vexpand">True</property> <property name="shadow_type">in</property> <child> - <object class="GtkViewport" id="viewport2"> + <object class="GtkViewport" id="viewport1"> <property name="visible">True</property> <property name="can_focus">False</property> <child> - <object class="GtkTreeView" id="vg_disk_view"> + <object class="GtkTreeView" id="disk_view"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="model">disk_store</property> - <property name="search_column">0</property> <child internal-child="selection"> - <object class="GtkTreeSelection" id="treeview-selection3"> + <object class="GtkTreeSelection" id="disk_view_selection"> <property name="mode">multiple</property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="disk_column1"> + <object class="GtkTreeViewColumn" id="disk_column"> <property name="spacing">6</property> <property name="title" translatable="yes">Disk</property> <property name="clickable">True</property> <child> - <object class="GtkCellRendererText" id="disk_renderer1"/> + <object class="GtkCellRendererText" id="disk_renderer"/> <attributes> <attribute name="text">0</attribute> </attributes> @@ -1742,12 +1970,12 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="capacity_column1"> + <object class="GtkTreeViewColumn" id="capacity_column"> <property name="spacing">6</property> <property name="title" translatable="yes">Capacity</property> <property name="clickable">True</property> <child> - <object class="GtkCellRendererText" id="capacity_renderer1"/> + <object class="GtkCellRendererText" id="capacity_renderer"/> <attributes> <attribute name="text">1</attribute> </attributes> @@ -1755,12 +1983,12 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="free_column1"> + <object class="GtkTreeViewColumn" id="free_column"> <property name="spacing">6</property> <property name="title" translatable="yes">Free</property> <property name="clickable">True</property> <child> - <object class="GtkCellRendererText" id="free_renderer1"/> + <object class="GtkCellRendererText" id="free_renderer"/> <attributes> <attribute name="text">2</attribute> </attributes> @@ -1768,12 +1996,12 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> <child> - <object class="GtkTreeViewColumn" id="id_column1"> + <object class="GtkTreeViewColumn" id="id_column"> <property name="spacing">6</property> <property name="title" translatable="yes">ID</property> <property name="clickable">True</property> <child> - <object class="GtkCellRendererText" id="id_renderer1"/> + <object class="GtkCellRendererText" id="id_renderer"/> <attributes> <attribute name="text">3</attribute> </attributes> @@ -1788,7 +2016,7 @@ until you click on the main menu's 'Begin Installation' button.</property> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">3</property> + <property name="position">2</property> </packing> </child> </object> @@ -1801,48 +2029,38 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> <action-widgets> - <action-widget response="0">vg_cancel_button</action-widget> - <action-widget response="1">vg_save_button</action-widget> + <action-widget response="0">cancel_button</action-widget> + <action-widget response="1">select_button</action-widget> </action-widgets> </object> - <object class="GtkListStore" id="disk_store"> - <columns> - <!-- column-name disk_col --> - <column type="gchararray"/> - <!-- column-name capacity_col --> - <column type="gchararray"/> - <!-- column-name free_col --> - <column type="gchararray"/> - <!-- column-name serial_col --> - <column type="gchararray"/> - <!-- column-name id_col --> - <column type="gint"/> - </columns> - </object> - <object class="GtkDialog" id="disks_dialog"> - <property name="width_request">400</property> - <property name="height_request">200</property> + <object class="GtkTextBuffer" id="help_text_buffer"/> + <object class="GtkDialog" id="help_dialog"> + <property name="width_request">600</property> + <property name="height_request">400</property> <property name="can_focus">False</property> <property name="border_width">5</property> + <property name="title" translatable="yes">Help</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> <property name="type_hint">dialog</property> - <property name="decorated">False</property> + <signal name="close" handler="on_close" swapped="no"/> <child internal-child="vbox"> - <object class="GtkBox" id="dialog-vbox4"> + <object class="GtkBox" id="dialog-vbox6"> <property name="can_focus">False</property> <property name="orientation">vertical</property> - <property name="spacing">6</property> + <property name="spacing">2</property> <child internal-child="action_area"> - <object class="GtkButtonBox" id="dialog-action_area4"> + <object class="GtkButtonBox" id="dialog-action_area6"> <property name="can_focus">False</property> <property name="layout_style">end</property> <child> - <object class="GtkButton" id="cancel_button"> - <property name="label" translatable="yes">_Cancel</property> + <object class="GtkButton" id="help_close_button"> + <property name="label">gtk-close</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="on_cancel_clicked" swapped="no"/> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_close" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -1850,21 +2068,6 @@ until you click on the main menu's 'Begin Installation' button.</property> <property name="position">0</property> </packing> </child> - <child> - <object class="GtkButton" id="select_button"> - <property name="label" translatable="yes">_Select</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="on_select_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> </object> <packing> <property name="expand">False</property> @@ -1874,127 +2077,23 @@ until you click on the main menu's 'Begin Installation' button.</property> </packing> </child> <child> - <object class="GtkBox" id="box1"> + <object class="GtkScrolledWindow" id="scrolledwindow3"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="disk_selection_title_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">CONFIGURE MOUNT POINT</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label11"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Select one or more disks this device may reside on.</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> <child> - <object class="GtkScrolledWindow" id="scrolledwindow1"> + <object class="GtkTextView" id="help_text_view"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport" id="viewport1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkTreeView" id="disk_view"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="model">disk_store</property> - <child internal-child="selection"> - <object class="GtkTreeSelection" id="disk_view_selection"> - <property name="mode">multiple</property> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="disk_column"> - <property name="spacing">6</property> - <property name="title" translatable="yes">Disk</property> - <property name="clickable">True</property> - <child> - <object class="GtkCellRendererText" id="disk_renderer"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="capacity_column"> - <property name="spacing">6</property> - <property name="title" translatable="yes">Capacity</property> - <property name="clickable">True</property> - <child> - <object class="GtkCellRendererText" id="capacity_renderer"/> - <attributes> - <attribute name="text">1</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="free_column"> - <property name="spacing">6</property> - <property name="title" translatable="yes">Free</property> - <property name="clickable">True</property> - <child> - <object class="GtkCellRendererText" id="free_renderer"/> - <attributes> - <attribute name="text">2</attribute> - </attributes> - </child> - </object> - </child> - <child> - <object class="GtkTreeViewColumn" id="id_column"> - <property name="spacing">6</property> - <property name="title" translatable="yes">ID</property> - <property name="clickable">True</property> - <child> - <object class="GtkCellRendererText" id="id_renderer"/> - <attributes> - <attribute name="text">3</attribute> - </attributes> - </child> - </object> - </child> - </object> - </child> - </object> - </child> + <property name="editable">False</property> + <property name="wrap_mode">word</property> + <property name="cursor_visible">False</property> + <property name="buffer">help_text_buffer</property> </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> </child> </object> <packing> - <property name="expand">False</property> + <property name="expand">True</property> <property name="fill">True</property> <property name="position">1</property> </packing> @@ -2002,11 +2101,9 @@ until you click on the main menu's 'Begin Installation' button.</property> </object> </child> <action-widgets> - <action-widget response="0">cancel_button</action-widget> - <action-widget response="1">select_button</action-widget> + <action-widget response="0">help_close_button</action-widget> </action-widgets> </object> - <object class="GtkTextBuffer" id="help_text_buffer"/> <object class="GtkEntryCompletion" id="mountPointCompletion"> <property name="model">mountPointStore</property> </object> @@ -2027,53 +2124,65 @@ until you click on the main menu's 'Begin Installation' button.</property> <column type="gboolean"/> <!-- column-name validForBTRFS --> <column type="gboolean"/> + <!-- column-name validForVG --> + <column type="gboolean"/> </columns> <data> <row> - <col id="0" translatable="yes">Single <span foreground="grey">(No RAID)</span></col> - <col id="1">False</col> + <col id="0" translatable="yes">None</col> + <col id="1">True</col> <col id="2">False</col> <col id="3">True</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID0 <span foreground="grey">(Performance)</span></col> <col id="1">True</col> <col id="2">True</col> <col id="3">True</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID1 <span foreground="grey">(Redundancy)</span></col> <col id="1">True</col> <col id="2">True</col> <col id="3">True</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID4 <span foreground="grey">(Error Checking)</span></col> <col id="1">False</col> <col id="2">True</col> <col id="3">False</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID5 <span foreground="grey">(Distributed Error Checking)</span></col> <col id="1">False</col> <col id="2">True</col> <col id="3">False</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID6 <span foreground="grey">(Redundant Error Checking)</span></col> <col id="1">False</col> <col id="2">True</col> <col id="3">False</col> + <col id="4">True</col> </row> <row> <col id="0" translatable="yes">RAID10 <span foreground="grey">(Performance, Redundancy)</span></col> <col id="1">False</col> <col id="2">True</col> <col id="3">False</col> + <col id="4">True</col> </row> </data> </object> <object class="GtkTreeModelFilter" id="raidStoreFiltered"> <property name="child_model">raidLevelStore</property> </object> + <object class="GtkTreeModelFilter" id="containerRaidStoreFiltered"> + <property name="child_model">raidLevelStore</property> + </object> </interface> diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 8fb8b74..60f4a43 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -21,18 +21,11 @@ #
# TODO: -# - Add a way for users to specify the names of subvols. # - 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. # - Tabbing behavior in the accordion is weird. -# - Update feature space costs when size spinner changes. # - Implement striping and mirroring for LVM. -# - Implement container management for btrfs. -# - If you click to add a mountpoint while editing a device the lightbox -# screenshot is taken prior to the ui update so the background shows the old -# size and free space while you're deciding on a size for the new device. -# - DeviceTree.populate brings back deleted devices when unlocking LUKS.
import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -59,6 +52,7 @@ from blivet.devicefactory import DEVICE_TYPE_BTRFS from blivet.devicefactory import DEVICE_TYPE_PARTITION from blivet.devicefactory import DEVICE_TYPE_MD from blivet.devicefactory import DEVICE_TYPE_DISK +from blivet.devicefactory import get_raid_level from blivet import findExistingInstallations from blivet.partitioning import doPartitioning from blivet.partitioning import doAutoPartition @@ -97,7 +91,12 @@ NOTEBOOK_UNEDITABLE_PAGE = 3 NOTEBOOK_INCOMPLETE_PAGE = 4
new_install_name = N_("New %s %s Installation") -new_vg_text = N_("Create a new volume group ...") +new_container_text = N_("Create a new %(container_type)s ...") +container_dialog_title = N_("CONFIGURE %(container_type)s") +container_dialog_text = N_("Please create a name for this %(container_type)s " + "and select at least one disk below.") +lvm_container_name = N_("Volume Group") +btrfs_container_name = N_("Volume") unrecoverable_error_msg = N_("Storage configuration reset due to unrecoverable " "error. Click for details.") device_configuration_error_msg = N_("Device reconfiguration failed. Click for " @@ -107,6 +106,15 @@ empty_mountpoint_msg = N_("Please enter a valid mountpoint.") invalid_mountpoint_msg = N_("That mount point is invalid. Try something else?") mountpoint_in_use_msg = N_("That mount point is already in use. Try something else?")
+raid_level_not_enough_disks_msg = N_("The RAID level you have selected (%s) " + "requires more disks (%d) than you " + "currently have selected (%d).") +empty_name_msg = N_("Please enter a valid name.") +invalid_name_msg = N_("That name is invalid. Try something else?") + +container_type_names = {DEVICE_TYPE_LVM: lvm_container_name, + DEVICE_TYPE_BTRFS: btrfs_container_name} + MOUNTPOINT_OK = 0 MOUNTPOINT_INVALID = 1 MOUNTPOINT_IN_USE = 2 @@ -328,19 +336,41 @@ class DisksDialog(GUIObject): def run(self): return self.window.run()
-class VolumeGroupDialog(GUIObject): - builderObjects = ["vg_dialog", "disk_store", "vg_disk_view"] - mainWidgetName = "vg_dialog" +class ContainerDialog(GUIObject): + builderObjects = ["container_dialog", "disk_store", "container_disk_view", + "containerRaidStoreFiltered", "containerRaidLevelLabel", + "containerRaidLevelCombo", "raidLevelStore", + "containerEncryptedCheckbox"] + mainWidgetName = "container_dialog" uiFile = "spokes/custom.glade"
def __init__(self, *args, **kwargs): self._disks = kwargs.pop("disks") free = kwargs.pop("free") - self.name = kwargs.pop("name") or "" self.selected = kwargs.pop("selected")[:] + self.name = kwargs.pop("name") or "" + self.device_type = kwargs.pop("device_type") + self.raid_level = kwargs.pop("raid_level") or None + self.encrypted = kwargs.pop("encrypted", False) + self.exists = kwargs.pop("exists", False) + self._error = None GUIObject.__init__(self, *args, **kwargs)
- self.builder.get_object("vg_name_entry").set_text(self.name) + # set up the dialog labels with device-type-specific text + container_type = container_type_names.get(self.device_type, + _("container")) + title_text = container_dialog_title % {"container_type": container_type.upper()} + title_label = self.builder.get_object("container_dialog_title_label") + title_label.set_text(title_text) + + dialog_text = container_dialog_text % {"container_type": container_type.lower()} + dialog_label = self.builder.get_object("container_dialog_label") + dialog_label.set_text(dialog_text) + + # populate the dialog widgets + name_entry = self.builder.get_object("container_name_entry") + name_entry.set_text(self.name) + name_entry.set_sensitive(not self.exists)
self._store = self.builder.get_object("disk_store") # populate the store @@ -351,22 +381,33 @@ class VolumeGroupDialog(GUIObject): disk.serial, disk.id])
- treeview = self.builder.get_object("vg_disk_view") + treeview = self.builder.get_object("container_disk_view") model = treeview.get_model() itr = model.get_iter_first()
selected_ids = [d.id for d in self.selected] - log.debug("selected: %s" % [d.name for d in self.selected]) - log.debug("selected: %s" % selected_ids) selection = treeview.get_selection() while itr: disk_id = model.get_value(itr, 4) - log.debug("store: %d" % disk_id) if disk_id in selected_ids: selection.select_iter(itr)
itr = model.iter_next(itr)
+ treeview.set_sensitive(not self.exists) + + # XXX how will this be related to the device encryption setting? + encryptCheckbutton = self.builder.get_object("containerEncryptedCheckbox") + encryptCheckbutton.set_active(self.encrypted) + encryptCheckbutton.set_sensitive(not self.exists) + + # set up the raid level combo + # XXX how will this be related to the device raid level setting? + raidStoreFilter = self.builder.get_object("containerRaidStoreFiltered") + raidStoreFilter.set_visible_func(self._raid_level_visible) + raidStoreFilter.refilter() + self._populate_raid() + def on_cancel_clicked(self, button): self.window.destroy()
@@ -376,14 +417,32 @@ class VolumeGroupDialog(GUIObject): return disk
def on_save_clicked(self, button): + if self.exists: + self.window.destroy() + return + # If no name was entered, quit the dialog as if they did nothing. - name = self.builder.get_object("vg_name_entry").get_text().strip() + name = self.builder.get_object("container_name_entry").get_text().strip() if not name: - self.window.destroy() + self._error = _(empty_name_msg) + self.builder.get_object("containerErrorLabel").set_text(self._error) + self.window.show_all() return
- treeview = self.builder.get_object("vg_disk_view") + treeview = self.builder.get_object("container_disk_view") model, paths = treeview.get_selection().get_selected_rows() + + raid_level = self._get_raid_level() + if raid_level: + md_level = mdraid.raidLevel(raid_level) + min_disks = mdraid.get_raid_min_members(md_level) + if len(paths) < min_disks: + self._error = (_(raid_level_not_enough_disks_msg) + % (raid_level, min_disks, len(paths))) + self.builder.get_object("containerErrorLabel").set_text(self._error) + self.window.show_all() + return + self.selected = [] for path in paths: itr = model.get_iter(path) @@ -391,11 +450,68 @@ class VolumeGroupDialog(GUIObject): self.selected.append(self._get_disk_by_id(disk_id))
self.name = name + self.raid_level = raid_level + self.encrypted = self.builder.get_object("containerEncryptedCheckbox").get_active()
+ self.builder.get_object("containerErrorLabel").set_text("") self.window.destroy()
def run(self): - return self.window.run() + while True: + self._error = None + rc = self.window.run() + if not self._error: + return rc + + def _raid_level_visible(self, model, itr, user_data): + # This is weird because for lvm's container-wide raid we use md. + if self.device_type == DEVICE_TYPE_LVM: + return model[itr][4] + elif self.device_type == DEVICE_TYPE_BTRFS: + return model[itr][3] + + def _get_raid_level(self): + combo = self.builder.get_object("containerRaidLevelCombo") + itr = combo.get_active_iter() + store = combo.get_model() + + if not itr: + return + + level = store[itr][0].split()[0].lower() + if level == "none": + level = None + + return level + + def _populate_raid(self): + """ Set up the raid-specific portion of the device details. """ + raid_label = self.builder.get_object("containerRaidLevelLabel") + raid_combo = self.builder.get_object("containerRaidLevelCombo") + + if self.device_type not in [DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS]: + for widget in [raid_label, raid_combo]: + widget.set_no_show_all(True) + widget.hide() + + return + + raid_level = self.raid_level + if not raid_level or raid_level == "single": + raid_level = "None" + + # Set a default RAID level in the combo. + for (i, row) in enumerate(raid_combo.get_model()): + log.debug("container dialog: raid level %s" % row[0]) + if row[0].upper().startswith(raid_level.upper()): + raid_combo.set_active(i) + break + + for widget in [raid_label, raid_combo]: + widget.set_no_show_all(False) + widget.show() + + raid_combo.set_sensitive(not self.exists)
class HelpDialog(GUIObject): builderObjects = ["help_dialog", "help_text_view", "help_text_buffer"] @@ -435,6 +551,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
self._device_disks = [] self._device_container_name = None + self._device_container_raid_level = None self._device_name_dict = {DEVICE_TYPE_LVM: None, DEVICE_TYPE_MD: None, DEVICE_TYPE_PARTITION: "", @@ -822,13 +939,12 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
page.show_all()
- def _update_btrfs_selectors(self): + def _update_selectors(self): """ Update all btrfs selectors' size properties. """ # we're only updating selectors in the new root. problem? page = self._accordion._find_by_title(self.translated_new_install_name).get_child() for selector in page.members: - if selector._device.type.startswith("btrfs"): - selectorFromDevice(selector._device, selector=selector) + selectorFromDevice(selector._device, selector=selector)
def _replace_device(self, *args, **kwargs): """ Create a replacement device and update the device selector. """ @@ -1009,8 +1125,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): md_level = mdraid.raidLevel(raid_level) min_disks = mdraid.get_raid_min_members(md_level) if len(self._device_disks) < min_disks: - error = _("The RAID level you have selected requires more " - "disks than you currently have selected.") + error = (_(raid_level_not_enough_disks_msg) + % (raid_level, min_disks, len(self._device_disks)))
if error: self.set_warning(error) @@ -1032,12 +1148,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): container_name = self._device_container_name container = factory.get_container() old_container_encrypted = False + old_container_raid_level = None old_container = None if not changed_device_type: old_container = factory.get_container(device=use_dev) if old_container: old_container_name = old_container.name old_container_encrypted = old_container.encrypted + old_container_raid_level = get_raid_level(old_container)
container = factory.get_container(name=container_name) if old_container and container_name != old_container.name: @@ -1046,10 +1164,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): log.debug("old container: %s" % old_container_name) log.debug("new container: %s" % container_name)
- # eventually this will be controlled by the "edit container" dialog - container_encrypted = getattr(container, "encrypted", False) + container_encrypted = self._device_container_encrypted + log.debug("old container encrypted: %s" % old_container_encrypted) + log.debug("new container encrypted: %s" % container_encrypted) changed_container_encrypted = (container_encrypted != old_container_encrypted)
+ container_raid_level = self._device_container_raid_level + log.debug("old container raid level: %s" % old_container_raid_level) + log.debug("new container raid level: %s" % container_raid_level) + changed_container_raid_level = (old_container_raid_level != container_raid_level) + # DISK SET old_disks = device.disks if hasattr(device, "req_disks") and not device.exists: @@ -1063,10 +1187,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): log.debug("old disks: %s" % [d.name for d in old_disks]) log.debug("new disks: %s" % [d.name for d in disks])
+ # XXX prevent multiple raid or encryption layers? + changed = (changed_name or changed_size or changed_device_type or changed_label or changed_mountpoint or changed_disk_set or changed_encryption or changed_raid_level or - changed_container or changed_container_encrypted) + changed_container or changed_container_encrypted or + changed_container_raid_level)
if not use_dev.exists: if not changed: @@ -1100,6 +1227,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): encrypted=encrypted, name=name, container_name=container_name, container_encrypted=container_encrypted, + container_raid_level=container_raid_level, device=_device, selector=selector) except StorageError as e: @@ -1126,6 +1254,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): name=old_name, container_name=old_container_name, container_encrypted=old_container_encrypted, + container_raid_level=old_container_raid_level, selector=selector) except StorageError as e: # failed to recover. @@ -1139,7 +1268,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._devices = self.__storage.devices
# update size props of all btrfs devices' selectors - self._update_btrfs_selectors() + self._update_selectors()
self._updateSpaceDisplay()
@@ -1194,7 +1323,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): selectorFromDevice(device, selector=selector)
# update size props of all btrfs devices' selectors - self._update_btrfs_selectors() + self._update_selectors() self._updateSpaceDisplay()
# it's possible that reformat is active but fstype is unchanged, in @@ -1327,25 +1456,22 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): if not itr: return
- return store[itr][0].split()[0].lower() + level = store[itr][0].split()[0].lower() + if level == "none": + level = None + + return level
- def _populate_raid(self, raid_level, size): + def _populate_raid(self, raid_level): """ Set up the raid-specific portion of the device details. """ device_type = self._get_current_device_type() log.debug("populate_raid: %s, %s" % (device_type, raid_level))
- # If size comes in as a Size, convert it to a number. A None is - # fine, though. - if size and isinstance(size, Size): - size = int(size.convertTo(spec="MB")) - raid_label = self.builder.get_object("raidLevelLabel") raid_combo = self.builder.get_object("raidLevelCombo")
if device_type == DEVICE_TYPE_MD: - base_level = "raid1" # FIXME: should be linear - elif device_type == DEVICE_TYPE_BTRFS: - base_level = "single" + base_level = "raid1" else: for widget in [raid_label, raid_combo]: widget.set_no_show_all(True) @@ -1413,10 +1539,20 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
if hasattr(use_dev, "vg"): self._device_container_name = use_dev.vg.name + self._device_container_raid_level = get_raid_level(use_dev.vg) + self._device_container_encrypted = use_dev.vg.encrypted + elif hasattr(use_dev, "volume"): + self._device_container_name = use_dev.volume.name + self._device_container_raid_level = get_raid_level(use_dev.volume) + self._device_container_encrypted = use_dev.volume.encrypted else: self._device_container_name = None + self._device_container_raid_level = None + self._device_container_encrypted = False
- log.debug("updated device_vg_name to %s" % self._device_container_name) + log.debug("updated device_container_name to %s" % self._device_container_name) + log.debug("updated device_container_raid_level to %s" % self._device_container_raid_level) + log.debug("updated device_container_encrypted to %s" % self._device_container_encrypted)
selectedDeviceLabel.set_text(selector.props.name) selectedDeviceDescLabel.set_text(self._description(selector.props.name)) @@ -1589,11 +1725,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): typeCombo.set_sensitive(not use_dev.exists) raidLevelCombo.set_sensitive(not use_dev.exists)
+ # FIXME: device encryption should be mutually exclusive with container + # encryption + + # FIXME: device raid should be mutually exclusive with container raid + # you can't encrypt a btrfs subvolume -- only the volume/container encryptCheckbox.set_sensitive(device_type != DEVICE_TYPE_BTRFS)
- self._populate_raid(raid_level, device.size) - self._populate_lvm(device=use_dev) + self._populate_raid(raid_level) + self._populate_container(device=use_dev) # do this last in case this was set sensitive in on_device_type_changed if use_dev.exists: nameEntry.set_sensitive(False) @@ -1948,19 +2089,25 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): return
self._device_disks = disks - self._populate_raid(self._get_raid_level(), size_from_entry(self._sizeEntry)) + self._populate_raid(self._get_raid_level())
- def run_vg_editor(self, vg=None, name=None): - if vg: - vg_name = vg.name + def run_container_editor(self, container=None, name=None): + if container: + container_name = container.name + raid_level = get_raid_level(container) elif name: - vg_name = name + container_name = name + raid_level = None
- dialog = VolumeGroupDialog(self.data, - name=vg_name, - disks=self._clearpartDevices, - free=self._currentFreeInfo, - selected=self._device_disks) + dialog = ContainerDialog(self.data, + device_type=self._get_current_device_type(), + name=container_name, + raid_level=self._device_container_raid_level, + encrypted=self._device_container_encrypted, + disks=self._clearpartDevices, + free=self._currentFreeInfo, + selected=self._device_disks, + exists=getattr(container, "exists", False))
with enlightbox(self.window, dialog.window): rc = dialog.run() @@ -1977,67 +2124,98 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self.window.show_all() return
- log.debug("new VG name: %s" % name) - if name != vg_name and name in self.__storage.names: + log.debug("new container name: %s" % name) + if name != container_name and name in self.__storage.names: self._error = _("Volume Group name %s is already in use. Not " "saving changes.") % name self.set_info(self._error) self.window.show_all() return
+ if (set(disks) != set(self._device_disks) or + name != container_name or + dialog.raid_level != self._device_container_raid_level or + dialog.encrypted != self._device_container_encrypted): + self._applyButton.set_sensitive(True) + + log.debug("new container raid level: %s" % dialog.raid_level) + log.debug("new container encrypted: %s" % dialog.encrypted) + self._device_disks = disks self._device_container_name = name + self._device_container_raid_level = dialog.raid_level + self._device_container_encrypted = dialog.encrypted
- def on_modify_vg_clicked(self, button): - vg_name = self.builder.get_object("volumeGroupCombo").get_active_text() + def on_modify_container_clicked(self, button): + container_name = self.builder.get_object("containerCombo").get_active_text()
- vg = self.__storage.devicetree.getDeviceByName(vg_name) + container = self.__storage.devicetree.getDeviceByName(container_name)
# pass the name along with any found vg since we could be modifying a # vg that hasn't been instantiated yet - self.run_vg_editor(vg=vg, name=vg_name) + self.run_container_editor(container=container, name=container_name)
- log.debug("%s -> %s" % (vg_name, self._device_container_name)) - if vg_name == self._device_container_name: + log.debug("%s -> %s" % (container_name, self._device_container_name)) + if container_name == self._device_container_name: return
- log.debug("renaming VG %s to %s" % (vg_name, self._device_container_name)) - if vg: - self.__storage.devicetree.names.remove(vg.name) - self.__storage.devicetree.names.append(self._device_container_name) - vg.name = self._device_container_name + log.debug("renaming container %s to %s" % (container_name, self._device_container_name)) + if container: + # btrfs volume name/label does not go in the name list + if container.name in self.__storage.devicetree.names: + self.__storage.devicetree.names.remove(container.name) + self.__storage.devicetree.names.append(self._device_container_name) + + container.name = self._device_container_name
- vg_combo = self.builder.get_object("volumeGroupCombo") - for idx, data in enumerate(vg_combo.get_model()): + container_combo = self.builder.get_object("containerCombo") + for idx, data in enumerate(container_combo.get_model()): # we're looking for the original vg name - if data[0] == vg_name: - vg_combo.remove(idx) - vg_combo.insert_text(idx, self._device_container_name) - vg_combo.set_active(idx) + if data[0] == container_name: + container_combo.remove(idx) + container_combo.insert_text(idx, self._device_container_name) + container_combo.set_active(idx) break
- def on_vg_changed(self, combo): - vg_name = combo.get_active_text() - log.debug("new vg selection: %s" % vg_name) - if vg_name is None: + self._update_selectors() + + def on_container_changed(self, combo): + container_name = combo.get_active_text() + log.debug("new container selection: %s" % container_name) + if container_name is None: return
- if vg_name == _(new_vg_text): + if container_name == combo.get_active_text(): + log.debug("spurious call -- bailing") + return + + device_type = self._get_current_device_type() + container_type = container_type_names[device_type].lower() + new_text = _(new_container_text) % {"container_type": container_type} + if container_name == new_text: # run the vg editor dialog with a default name and disk set hostname = self.data.network.hostname name = self.__storage.suggestContainerName(hostname=hostname) - self.run_vg_editor(name=name) + self.run_container_editor(name=name) for idx, data in enumerate(combo.get_model()): - if data[0] == _(new_vg_text): + if data[0] == new_text: combo.insert_text(idx, self._device_container_name) - combo.set_active(idx) - break + combo.set_active(idx) # triggers a call to this method + return + else: + self._device_container_name = container_name + + container = self.__storage.devicetree.getDeviceByName(self._device_container_name) + container_exists = getattr(container, "exists", False) # might not be in the tree + + if container: + self._device_container_raid_level = get_raid_level(container) + self._device_container_encrypted = container.encrypted else: - self._device_container_name = vg_name + self._device_container_raid_level = None + self._device_container_encrypted = self.data.autopart.encrypted
- vg = self.__storage.devicetree.getDeviceByName(self._device_container_name) - vg_exists = getattr(vg, "exists", False) # might not be in the tree - self.builder.get_object("modifyVGButton").set_sensitive(not vg_exists) + self.builder.get_object("modifyContainerButton").set_sensitive(not container_exists)
def _save_current_selector(self): log.debug("current selector: %s" % self._current_selector._device) @@ -2236,7 +2414,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): hasattr(fmt, "label")) mountPointEntry.set_sensitive(fmt.mountable)
- def _populate_lvm(self, device=None): + def _populate_container(self, device=None): """ Set up the vg widgets for lvm or hide them for other types. """ device_type = self._get_current_device_type() if device is None: @@ -2244,49 +2422,65 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): return
device = self._current_selector._device + if isinstance(device, LUKSDevice): + device = device.slave
- vg_combo = self.builder.get_object("volumeGroupCombo") - vg_button = self.builder.get_object("modifyVGButton") - vg_label = self.builder.get_object("volumeGroupLabel") - if device_type == DEVICE_TYPE_LVM: + container_combo = self.builder.get_object("containerCombo") + container_button = self.builder.get_object("modifyContainerButton") + container_label = self.builder.get_object("containerLabel") + if device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS): # set up the vg widgets and then bail out - if self._device_container_name: - default_vg = self._device_container_name + if devicefactory.get_device_type(device) == device_type: + _device = device else: - with ui_storage_logger(): - factory = devicefactory.get_device_factory(self.__storage, - DEVICE_TYPE_LVM, - 0) - container = factory.get_container() - default_vg = getattr(container, "name", None) - - log.debug("default vg is %s" % default_vg) - vg_combo.remove_all() - vgs = self.__storage.vgs - for vg in vgs: - vg_combo.append_text(vg.name) - if default_vg and vg.name == default_vg: - vg_combo.set_active(vgs.index(vg)) - - if default_vg is None: + _device = None + + with ui_storage_logger(): + factory = devicefactory.get_device_factory(self.__storage, + device_type, + 0) + container = factory.get_container(device=_device) + default_container = getattr(container, "name", None) + + container_type_text = container_type_names[device_type] + container_label.set_text(container_type_text.title()) + container_combo.remove_all() + if device_type == DEVICE_TYPE_LVM: + containers = self.__storage.vgs + else: + containers = self.__storage.btrfsVolumes + + default_seen = False + for c in containers: + container_combo.append_text(c.name) + if default_container and c.name == default_container: + default_seen = True + container_combo.set_active(containers.index(c)) + + if default_container is None: hostname = self.data.network.hostname - default_vg = self.__storage.suggestContainerName(hostname=hostname) - vg_combo.append_text(default_vg) - vg_combo.set_active(len(vg_combo.get_model()) - 1) + default_container = self.__storage.suggestContainerName(hostname=hostname) + + log.debug("default container is %s" % default_container) + self._device_container_name = default_container + + if not default_seen: + container_combo.append_text(default_container) + container_combo.set_active(len(container_combo.get_model()) - 1)
- vg_combo.append_text(_(new_vg_text)) - if default_vg is None: - vg_combo.set_active(len(vg_combo.get_model()) - 1) + container_combo.append_text(_(new_container_text) % {"container_type": container_type_text.lower()}) + if default_container is None: + container_combo.set_active(len(container_combo.get_model()) - 1)
- for widget in [vg_label, vg_combo, vg_button]: + for widget in [container_label, container_combo, container_button]: widget.set_no_show_all(False) widget.show()
# make the combo and button insensitive for existing LVs - can_change_vg = (device is not None and not device.exists) - vg_combo.set_sensitive(can_change_vg) + can_change_container = (device is not None and not device.exists) + container_combo.set_sensitive(can_change_container) else: - for widget in [vg_label, vg_combo, vg_button]: + for widget in [container_label, container_combo, container_button]: widget.set_no_show_all(True) widget.hide()
@@ -2334,8 +2528,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): raidStoreFilter = self.builder.get_object("raidStoreFiltered") raidStoreFilter.refilter()
- self._populate_raid(raid_level, size_from_entry(self._sizeEntry)) - self._populate_lvm() + self._populate_raid(raid_level) + self._populate_container()
nameEntry = self.builder.get_object("nameEntry") nameEntry.set_sensitive(new_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_MD))
On Mon, 2013-04-22 at 10:22 -0500, David Lehman wrote:
Add ability to manage btrfs volumes in addition to lvm vgs.
Move container-wide raid and encryption settings into the edit container dialog.
pyanaconda/ui/gui/spokes/custom.glade | 863 +++++++++++++++++++--------------- pyanaconda/ui/gui/spokes/custom.py | 436 ++++++++++++----- 2 files changed, 801 insertions(+), 498 deletions(-)
def __init__(self, *args, **kwargs): self._disks = kwargs.pop("disks") free = kwargs.pop("free")
self.name = kwargs.pop("name") or "" self.selected = kwargs.pop("selected")[:]
self.name = kwargs.pop("name") or ""self.device_type = kwargs.pop("device_type")self.raid_level = kwargs.pop("raid_level") or Noneself.encrypted = kwargs.pop("encrypted", False)self.exists = kwargs.pop("exists", False)self._error = None GUIObject.__init__(self, *args, **kwargs)
I'm probably missing something here, but what is the purpose of these 'kwargs.pop(...) or ...' lines? dict.pop raises an exception if the key is not in the dict. So are these lines only for conversion between "" and None and vice versa? Or should there be 'kwargs.pop(..., ...)' instead? At least a comment would be nice.
level = store[itr][0].split()[0].lower()
Could you please split this line (and the same one somewhere below) into two using a temporary variable? It would give a context to the values it operates on.
On Mon, 2013-04-22 at 10:22 -0500, David Lehman wrote:
This makes the "edit VG" stuff into "edit container" where container could be either an LVM VG or a BTRFS volume. It moves container-wide settings like encryption and raid into the container dialog so users can have some sense of what they're setting up (encrypt a single lv versus encrypt the entire vg).
This also adds support for creating lvm-on-md, which the user can get by setting a raid level other than None for their VG.
Some things that probably need to be done, but not necessarily now:
- make encryption of an lv and its vg mutually exclusive
- ditto for raid (don't allow a striped lv in a vg that has md pvs)
I spent the better part of two weeks testing this, but that should provide little comfort given the number of paths through custom storage.
These all look good to me as far as I can understand them.
anaconda-patches@lists.fedorahosted.org