[PATCH 9/9][anaconda] Container management improvements.

David Lehman dlehman at redhat.com
Mon Apr 22 15:22:56 UTC 2013


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 &lt;span foreground="grey"&gt;(No RAID)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Performance)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Redundancy)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Error Checking)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Distributed Error Checking)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Redundant Error Checking)&lt;/span&gt;</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 &lt;span foreground="grey"&gt;(Performance, Redundancy)&lt;/span&gt;</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))
-- 
1.8.1.4



More information about the anaconda-patches mailing list