For a partition, the selected disks indicate acceptable options for
where the partition ends up. For btrfs, raid, and lvm it indicates
the set of disks on which member/pv devices will be created.
---
pyanaconda/storage/__init__.py | 67 +++++++++---
pyanaconda/ui/gui/spokes/custom.glade | 201 +++++++++++++++++++++++++++++++++
pyanaconda/ui/gui/spokes/custom.py | 121 ++++++++++++++++++--
3 files changed, 361 insertions(+), 28 deletions(-)
diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py
index 8691bef..d3b334c 100644
--- a/pyanaconda/storage/__init__.py
+++ b/pyanaconda/storage/__init__.py
@@ -1782,8 +1782,9 @@ class Storage(object):
return []
# set up member devices
- container_size = 0
+ container_size = factory.device_size
add_disks = []
+ remove_disks = []
if members is None:
members = []
@@ -1791,23 +1792,19 @@ class Storage(object):
if container:
members = container.parents[:]
- if container:
- log.debug("using container %s with %d devices" % (container.name,
- len(self.devicetree.getChildren(container))))
- container_size = factory.container_size_func(container, device)
- log.debug("raw container size reported as %d" % container_size)
-
- if members:
+ # XXX how can we detect/handle failure to use one or more of the disks?
+ if container 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.
_disks = list(set([d for m in members for d in m.disks]))
- # see if factory.disks contains disks not already in use
add_disks = [d for d in factory.disks if d not in _disks]
- else:
+ remove_disks = [d for d in _disks if d not in factory.disks]
+ elif not container:
+ # new container, so use the factory's disk set
add_disks = factory.disks
- device_space = factory.device_size
- log.debug("device requires %d" % device_space)
- container_size += device_space
base_size = max(1, getFormat(factory.member_format).minSize)
# XXX TODO: multiple member devices per disk
@@ -1815,8 +1812,21 @@ class Storage(object):
# prepare already-defined member partitions for reallocation
for member in members[:]:
if isinstance(member, LUKSDevice):
+ if member.slave.disk in remove_disks:
+ container.removeMember(member)
+ self.destroyDevice(member)
+ members.remove(member)
+
member = member.slave
+ 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
@@ -1850,6 +1860,12 @@ class Storage(object):
if container:
container.addMember(member)
+ if container:
+ log.debug("using container %s with %d devices" % (container.name,
+ len(self.devicetree.getChildren(container))))
+ container_size = factory.container_size_func(container, device)
+ log.debug("raw container size reported as %d" % container_size)
+
log.debug("adding a %s with size %d" % (factory.set_class.__name__,
container_size))
size_set = factory.set_class(members, container_size)
@@ -1988,6 +2004,13 @@ class Storage(object):
# TODO: striping, mirroring, &c
# TODO: non-partition members (pv-on-md)
+ # setContainerMembers can modify these, so save them now
+ old_size = None
+ old_disks = []
+ if device:
+ old_size = device.size
+ old_disks = device.disks[:]
+
members = []
if device and device.type == "mdarray":
members = device.parents
@@ -2045,19 +2068,27 @@ class Storage(object):
if device:
# We are adjusting the size of a device. The StorageDevice instance
# exists, but the underlying device does not.
- old_size = device.size
e = None
try:
factory.post_create()
except StorageError as e:
log.error("device post-create method failed: %s" % e)
else:
- if not device.size:
- e = StorageError("failed to adjust device")
+ if device.size <= device.format.minSize:
+ e = StorageError("failed to adjust device -- not enough free
space in specified disks?")
if e:
# Clean up by reverting the device to its previous size.
factory.size = old_size
+ factory.disks = old_disks
+ try:
+ self.setContainerMembers(container, factory,
+ members=members, device=device)
+ except StorageError as e:
+ # yes, we're replacing e here.
+ log.error("failed to revert device size: %s" % e)
+ raise ErrorRecoveryFailure("failed to revert device size")
+
factory.set_device_size(container, device=device)
try:
factory.post_create()
@@ -3208,6 +3239,10 @@ class PartitionFactory(DeviceFactory):
device.req_max_size = size
device.req_grow = size > base_size
+ # this probably belongs somewhere else but this is our chance to
+ # update the disk set
+ device.req_disks = self.disks[:]
+
return size
class BTRFSFactory(DeviceFactory):
diff --git a/pyanaconda/ui/gui/spokes/custom.glade
b/pyanaconda/ui/gui/spokes/custom.glade
index 5134c77..d36976d 100644
--- a/pyanaconda/ui/gui/spokes/custom.glade
+++ b/pyanaconda/ui/gui/spokes/custom.glade
@@ -1615,6 +1615,207 @@ you'll be able to view their details here.</property>
</object>
</child>
</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>
+ <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-vbox1">
+ <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_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label">_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="select_button">
+ <property name="label">_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>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <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="disk_selection_title_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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="label1">
+ <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>
+ <child>
+ <object class="GtkScrolledWindow"
id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</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>
+ </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">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">cancel_button</action-widget>
+ <action-widget response="1">select_button</action-widget>
+ </action-widgets>
+ </object>
<object class="GtkImage" id="removeImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index dfa7b52..9c9d872 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -279,6 +279,59 @@ class ConfirmDeleteDialog(GUIObject):
def run(self):
return self.window.run()
+class DisksDialog(GUIObject):
+ builderObjects = ["disks_dialog", "disk_store",
"disk_view"]
+ mainWidgetName = "disks_dialog"
+ uiFile = "spokes/custom.glade"
+
+ def __init__(self, *args, **kwargs):
+ self._disks = kwargs.pop("disks")
+ free = kwargs.pop("free")
+ self.selected = kwargs.pop("selected")[:]
+ GUIObject.__init__(self, *args, **kwargs)
+ self._store = self.builder.get_object("disk_store")
+ # populate the store
+ for disk in self._disks:
+ self._store.append([disk.description,
+ str(Size(spec="%dMB" % disk.size)),
+ str(free[disk.name][0]),
+ disk.serial,
+ disk.id])
+
+ treeview = self.builder.get_object("disk_view")
+ model = treeview.get_model()
+ itr = model.get_iter_first()
+ selected_ids = [d.id for d in self.selected]
+ selection = treeview.get_selection()
+ while itr:
+ disk_id = model.get_value(itr, 4)
+ if disk_id in selected_ids:
+ selection.select_iter(itr)
+
+ itr = model.iter_next(itr)
+
+ def on_cancel_clicked(self, button):
+ self.window.destroy()
+
+ def _get_disk_by_id(self, disk_id):
+ for disk in self._disks:
+ if disk.id == disk_id:
+ return disk
+
+ def on_select_clicked(self, button):
+ treeview = self.builder.get_object("disk_view")
+ model, paths = treeview.get_selection().get_selected_rows()
+ self.selected = []
+ for path in paths:
+ itr = model.get_iter(path)
+ disk_id = model.get_value(itr, 4)
+ self.selected.append(self._get_disk_by_id(disk_id))
+
+ self.window.destroy()
+
+ def run(self):
+ return self.window.run()
+
class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
builderObjects = ["customStorageWindow", "sizeAdjustment",
"partitionStore",
@@ -298,6 +351,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
self._fs_types = [] # list of supported fstypes
self._unused_devices = None # None indicates uninitialized
self._free_space = Size(bytes=0)
+ self._device_disks = []
self._initialized = False
@@ -1004,15 +1058,22 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
changed_raid_level = (current_device_type == device_type and
device_type in (None, AUTOPART_TYPE_BTRFS) and
old_raid_level != raid_level)
+ changed_disk_set = (set(device.disks) != set(self._device_disks))
if changed_device_type or changed_raid_level:
if changed_device_type:
log.info("changing device type from %s to %s"
% (current_device_type, device_type))
- else:
+
+ if changed_raid_level:
log.info("changing raid level from %s to %s"
% (old_raid_level, raid_level))
+ if changed_disk_set:
+ log.info("changing disk set from %s to %s"
+ % ([d.name for d in device.disks],
+ [d.name for d in self._device_disks]))
+
# remove the current device
self.clear_errors()
root = self._current_selector._root
@@ -1022,13 +1083,11 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
return
with ui_storage_logger():
- # Use any disks with space in addition to any disks used by
- # a defined container.
- disks = [d for d in self._clearpartDevices
- if getattr(d.format, "free", 0) > 500]
+ disks = self._device_disks[:]
container = self.__storage.getContainer(factory)
- if container:
- disks = list(set(disks).union(container.disks))
+ if container and changed_device_type:
+ log.debug("overriding disk set with container's")
+ disks = container.disks[:]
log.debug("disks: %s" % [d.name for d in disks])
try:
self._replace_device(device_type, size, fstype=fs_type,
@@ -1101,7 +1160,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
## SIZE
##
# new size means resize for existing devices and adjust for new ones
- if int(size) != int(device.size):
+ if int(size) != int(device.size) or changed_disk_set:
self.clear_errors()
old_size = device.size
if device.exists and device.resizable:
@@ -1121,11 +1180,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
log.debug("new size: %s" % device.size)
log.debug("target size: %s" % device.targetSize)
else:
+ if changed_disk_set:
+ log.info("changing disk set from %s to %s"
+ % ([d.name for d in device.disks],
+ [d.name for d in self._device_disks]))
+
with ui_storage_logger():
try:
self.__storage.newDevice(device_type, size,
device=device,
- disks=device.disks,
+ disks=self._device_disks[:],
raid_level=raid_level)
except ErrorRecoveryFailure as e:
self._error = e
@@ -1320,7 +1384,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
def _get_raid_disabled_features(self, raid_level):
""" Return a list of disabled features based on raid level.
"""
disabled = raid_disabled_features.get(raid_level, [])
- disk_count = len(self._clearpartDevices)
+ disk_count = len(self._device_disks)
# go through each feature's reference raid level, filtering any that
# require more disks than we have available
for feature in raid_features:
@@ -1365,7 +1429,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# this device with various raid features enabled.
with ui_storage_logger():
factory = self.__storage.getDeviceFactory(factory_type, size,
- disks=self._clearpartDevices,
+ disks=self._device_disks,
raid_level=base_level)
widget_dict = self._get_raid_widget_dict(device_type)
@@ -1437,6 +1501,9 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
else:
use_dev = device
+ self._device_disks = getattr(use_dev, "req_disks", use_dev.disks)
+ log.debug("udpated device_disks to %s" % [d.name for d in
self._device_disks])
+
selectedDeviceLabel.set_text(selector.props.name)
selectedDeviceDescLabel.set_text(self._description(selector.props.name))
@@ -1816,7 +1883,37 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
dialog.run()
def on_configure_clicked(self, button):
- pass
+ selector = self._current_selector
+ if not selector:
+ return
+
+ device = selector._device
+ if device.exists:
+ return
+
+ self.clear_errors()
+
+ dialog = DisksDialog(self.data,
+ disks=self._clearpartDevices,
+ free=self._currentFreeInfo,
+ selected=self._device_disks)
+ with enlightbox(self.window, dialog.window):
+ rc = dialog.run()
+
+ if rc == 0:
+ return
+
+ disks = dialog.selected
+ log.debug("new disks for %s: %s" % (device.name,
+ [d.name for d in disks]))
+ if not disks:
+ self._error = "No disks selected. Keeping previous disk set."
+ self.window.set_info(Gtk.MessageType.INFO,
+ self._error)
+ self.window.show_all()
+ return
+
+ self._device_disks = disks
def on_selector_clicked(self, selector):
if not self._initialized:
--
1.7.7.6