diff options
40 files changed, 1239 insertions, 671 deletions
diff --git a/anaconda.spec.in b/anaconda.spec.in index 7deffe3ec..ebde10d3d 100644 --- a/anaconda.spec.in +++ b/anaconda.spec.in @@ -293,6 +293,70 @@ update-desktop-database &> /dev/null || : /usr/lib/dracut/modules.d/80%{name}/* %changelog +* Mon Aug 13 2012 Chris Lumens <clumens@redhat.com> - 18.4-1 +- dracut: fix inst.ks.sendmac (#826657) (wwoods) +- dracut: suppress ks errors from missing %include (wwoods) +- dracut: add comment to run_kickstart() (wwoods) +- Remove unused writeKS methods. (clumens) +- Only show unused devices that haven't been removed/deleted. (dlehman) +- Don't unexpand already-expanded pages when trying to expand them again. + (dlehman) +- Make parents of hidden devices appear to be leaves. (dlehman) +- Remove the right device name from the lvm filter when unhiding device. + (dlehman) +- Take configured filesystems into account when checking package space. + (dlehman) +- Make sure the ksdata autopart type matches the storage one. (dlehman) +- Base auto-generated name prefixes on productName, not device type. (dlehman) +- Remove shrink code that was a workaround for the old ui flow. (dlehman) +- Remove old ui progress args from devicelibs.btrfs. (dlehman) +- Make sure we allocate partitions and grow lvm as needed in kickstart. + (dlehman) +- Streamline autopart request setup slightly. (dlehman) +- Make it possible to call setUpBootLoader safely at any time. (dlehman) +- Move setup of new partition weight arg to Storage.newPartition. (dlehman) +- Use a copy of the main Storage instance during custom partitioning. (dlehman) +- Track requested sizes of btrfs subvols. (dlehman) +- Add a method to retrieve a devicetree device by id number. (dlehman) +- Fix DiskLabel so it can be deep-copied. (dlehman) +- Add a method to produce a deep copy of a Storage instance. (dlehman) +- Fix subtraction for Size. (dlehman) +- Add support for creating device based on a top-down specification. (dlehman) +- Add size-set managers to keep a set of growable requests in sync. (dlehman) +- Add a function to estimate required disk space for an md array. (dlehman) +- Add a method to estimate disk space needs for a new logical volume. (dlehman) +- Add a convenience method for new btrfs subvols and drop subvol size args. + (dlehman) +- Use the UEFI shim to load grub. (pjones) +- Check that Gtk.main is not already running before starting another one + (vpodzime) +- With tmux, we no longer need to start up a shell during VNC installs. + (clumens) +- We no longer need getkeymaps, mapshdr, or readmap. (clumens) +- Remove the last references to isysLoadKeymap. (clumens) +- remove Security class (bcl) +- replace lokkit for selinux settings (#815540) (bcl) +- tests: Add tests for new SimpleConfigFile features (bcl) +- tests: cleanup whitespace in simpleconfig_test.py (bcl) +- simpleconfig: rewrite to better support commented config files (bcl) +- If the anaconda process crashes, don't delete its window. (clumens) +- On interactive installs, default the root account to locked. (clumens) +- Make the keyboard layout test a big text area instead of a single line. + (clumens) +- Remove our loadKeymap code from isys (vpodzime) +- Replace system-config-keyboard with our methods using ksdata.keyboard + (vpodzime) +- A little fix of newui -> master merge (iscsi offload devices) (rvykydal) +- Require new version of python-meh (vpodzime) +- Modify kernelPackages to select the right kernel for ARM systems. (dmarlin) +- ARM: clean up the kernel selection to be consistent with the rest of the code + (dennis) +- add command line option to set the arm platform. (dennis) +- Add support to determine the ARM processor variety and select the correct + kernel to install. (dmarlin) +- TODO list updates. (clumens) +- Sent pot file updates to the master branch in transifex, not f17. (clumens) + * Fri Aug 03 2012 Chris Lumens <clumens@redhat.com> - 18.3-1 - New graphical user interface. - Removed loader. diff --git a/configure.ac b/configure.ac index 530dd6778..4ed38adc6 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ m4_define(python_required_version, 2.5) AC_PREREQ([2.63]) -AC_INIT([anaconda], [18.3], [anaconda-devel-list@redhat.com]) +AC_INIT([anaconda], [18.4], [anaconda-devel-list@redhat.com]) AM_INIT_AUTOMAKE([foreign no-dist-gzip dist-bzip2]) AC_CONFIG_HEADERS([config.h]) diff --git a/dracut/Makefile.am b/dracut/Makefile.am index 032476676..8c56bf3eb 100644 --- a/dracut/Makefile.am +++ b/dracut/Makefile.am @@ -29,6 +29,7 @@ dist_dracut_SCRIPTS = module-setup.sh \ kickstart-genrules.sh \ updates-genrules.sh \ anaconda-udevprop.sh \ + anaconda-ks-sendheaders.sh \ anaconda-netroot.sh \ anaconda-diskroot \ anaconda-copy-ks.sh \ diff --git a/dracut/anaconda-ks-sendheaders.sh b/dracut/anaconda-ks-sendheaders.sh new file mode 100755 index 000000000..aa178cea3 --- /dev/null +++ b/dracut/anaconda-ks-sendheaders.sh @@ -0,0 +1,32 @@ +#/bin/sh +# anaconda-ks-sendheaders.sh - set various HTTP headers for kickstarting + +[ -f /tmp/.ks_sendheaders ] && return + +# inst.ks.sendmac: send MAC addresses in HTTP headers +if getargbool 0 kssendmac inst.ks.sendmac; then + ifnum=0 + for ifname in /sys/class/net/*; do + [ -e "$ifname/address" ] || continue + mac=$(cat $ifname/address) + ifname=${ifname#/sys/class/net/} + # TODO: might need to choose devices better + if [ "$ifname" != "lo" ] && [ -n "$mac" ]; then + # set_http_header is from url-lib.sh, sourced earlier + set_http_header "X-RHN-Provisioning-MAC-$ifnum" "$ifname $mac" + ifnum=$(($ifnum+1)) + fi + done +fi + +# inst.ks.sendsn: send system serial number in HTTP headers +if getargbool 0 kssendsn inst.ks.sendsn; then + system_serial=$(cat /sys/class/dmi/id/product_serial 2>/dev/null) + if [ -z "$system_serial" ]; then + warn "inst.ks.sendsn: can't find system serial number" + else + set_http_header "X-System-Serial-Number" "$system_serial" + fi +fi + +> /tmp/.ks_sendheaders diff --git a/dracut/anaconda-lib.sh b/dracut/anaconda-lib.sh index a045bab0f..98b80e6e2 100755 --- a/dracut/anaconda-lib.sh +++ b/dracut/anaconda-lib.sh @@ -183,6 +183,8 @@ parse_kickstart() { # # Really what we want to do here is just start over from the "cmdline" # phase, but since we can't do that, we'll kind of fake it. +# +# XXX THIS IS KIND OF A GROSS HACK AND WE NEED A BETTER WAY TO DO IT run_kickstart() { local do_disk="" do_net="" diff --git a/dracut/module-setup.sh b/dracut/module-setup.sh index 39ecc58d3..78da00244 100755 --- a/dracut/module-setup.sh +++ b/dracut/module-setup.sh @@ -26,6 +26,7 @@ install() { inst_hook pre-udev 40 "$moddir/kickstart-genrules.sh" inst_hook pre-udev 40 "$moddir/updates-genrules.sh" inst_hook pre-trigger 40 "$moddir/anaconda-udevprop.sh" + inst_hook initqueue/settled 00 "$moddir/anaconda-ks-sendheaders.sh" inst_hook initqueue/online 80 "$moddir/anaconda-netroot.sh" inst "$moddir/anaconda-diskroot" "/sbin/anaconda-diskroot" inst_hook pre-pivot 99 "$moddir/anaconda-copy-ks.sh" diff --git a/dracut/parse-anaconda-kickstart.sh b/dracut/parse-anaconda-kickstart.sh index afc3d6356..d445e2751 100755 --- a/dracut/parse-anaconda-kickstart.sh +++ b/dracut/parse-anaconda-kickstart.sh @@ -31,33 +31,3 @@ case "${kickstart%%:*}" in ;; esac export kickstart - -# inst.ks.sendmac: send MAC addresses in HTTP headers -set_ks_sendmac() { - local ifnum=0 mac="" ifname="" - for ifname in /sys/class/net/*; do - [ -e "$ifname/address" ] || continue - mac=$(cat $ifname/address) - ifname=${ifname#/sys/class/net/} - # TODO: might need to choose devices better - if [ "$ifname" != "lo" ] && [ -n "$mac" ]; then - set_http_header "X-RHN-Provisioning-MAC-$ifnum" "$ifname $mac" - ifnum=$(($ifnum+1)) - fi - done -} -if getargbool 0 kssendmac inst.ks.sendmac; then - # needs to run in mainloop (after modules load etc.) - initqueue --unique --settled --onetime --name "01-sendmac" set_ks_sendmac -fi - -# inst.ks.sendsn: send system serial number as HTTP header -if getargbool 0 kssendsn inst.ks.sendsn; then - # doesn't need to wait - dmi is builtin - system_serial=$(cat /sys/class/dmi/id/product_serial 2>/dev/null) - if [ -z "$system_serial" ]; then - warn "inst.ks.sendsn: can't find system serial number" - else - set_http_header "X-System-Serial-Number" "$system_serial" - fi -fi diff --git a/dracut/parse-kickstart b/dracut/parse-kickstart index 13a0e8f43..0cbb4368a 100755 --- a/dracut/parse-kickstart +++ b/dracut/parse-kickstart @@ -285,7 +285,7 @@ def ksnet_to_ifcfg(net, filename=None): def process_kickstart(ksfile): handler = DracutHandler() handler.ksdevice = os.environ.get('ksdevice') - parser = KickstartParser(handler) + parser = KickstartParser(handler, missingIncludeIsFatal=False) log.info("processing kickstart file %s", ksfile) processed_file = preprocessKickstart(ksfile) try: diff --git a/po/anaconda.pot b/po/anaconda.pot index 9e5267a0a..89abc5ed0 100644 --- a/po/anaconda.pot +++ b/po/anaconda.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: anaconda 18.2\n" +"Project-Id-Version: anaconda 18.3\n" "Report-Msgid-Bugs-To: anaconda-devel-list@redhat.com\n" -"POT-Creation-Date: 2012-08-03 16:44-0400\n" +"POT-Creation-Date: 2012-08-13 15:30-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -18,28 +18,24 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: anaconda:357 -msgid "Press <enter> for a shell" -msgstr "" - -#: anaconda:371 +#: anaconda:348 #, c-format msgid "" "%s requires %s MB of memory to install, but you only have %s MB on this " "machine.\n" msgstr "" -#: anaconda:373 +#: anaconda:350 #, c-format msgid "" "The %s graphical installer requires %s MB of memory, but you only have %s MB." msgstr "" -#: anaconda:378 +#: anaconda:355 msgid "Not enough RAM" msgstr "" -#: anaconda:379 +#: anaconda:356 msgid "" " Try the text mode installer by running:\n" "\n" @@ -48,41 +44,41 @@ msgid "" " from a root terminal." msgstr "" -#: anaconda:382 +#: anaconda:359 msgid " Starting text mode." msgstr "" -#: anaconda:409 pyanaconda/rescue.py:288 pyanaconda/rescue.py:321 +#: anaconda:386 pyanaconda/rescue.py:288 pyanaconda/rescue.py:321 #: pyanaconda/rescue.py:334 pyanaconda/rescue.py:402 pyanaconda/rescue.py:418 #: pyanaconda/rescue.py:429 msgid "OK" msgstr "" -#: anaconda:492 +#: anaconda:469 msgid "Would you like to use VNC?" msgstr "" -#: anaconda:493 +#: anaconda:470 msgid "" "Text mode provides a limited set of installation options. It does not allow " "you to specify your own partitioning layout or package selections. Would you " "like to use VNC mode instead?" msgstr "" -#: anaconda:514 +#: anaconda:491 msgid "DISPLAY variable not set. Starting text mode." msgstr "" -#: anaconda:522 +#: anaconda:499 msgid "Graphical installation is not available. Starting text mode." msgstr "" -#: anaconda:647 +#: anaconda:624 #, c-format msgid "Please ssh install@%s to begin the install." msgstr "" -#: anaconda:649 +#: anaconda:626 msgid "Please ssh install@<host> to continue installation." msgstr "" @@ -201,17 +197,17 @@ msgstr "" msgid "In interactive step can't continue. (%s)" msgstr "" -#: pyanaconda/constants.py:52 +#: pyanaconda/constants.py:57 msgid "" "An unhandled exception has occurred. This is most likely a bug. Please " "save a copy of the detailed exception and file a bug report" msgstr "" -#: pyanaconda/constants.py:58 +#: pyanaconda/constants.py:63 msgid " with the provider of this software." msgstr "" -#: pyanaconda/constants.py:62 +#: pyanaconda/constants.py:67 #, python-format msgid " against anaconda at %s" msgstr "" @@ -259,31 +255,31 @@ msgstr "" #: pyanaconda/yuminstall.py:1056 pyanaconda/yuminstall.py:1108 #: pyanaconda/yuminstall.py:1114 pyanaconda/yuminstall.py:1270 #: pyanaconda/yuminstall.py:1285 pyanaconda/yuminstall.py:1340 -#: pyanaconda/yuminstall.py:1530 pyanaconda/yuminstall.py:1563 -#: pyanaconda/yuminstall.py:1586 +#: pyanaconda/yuminstall.py:1535 pyanaconda/yuminstall.py:1568 +#: pyanaconda/yuminstall.py:1591 msgid "_Exit installer" msgstr "" -#: pyanaconda/iutil.py:848 +#: pyanaconda/iutil.py:865 msgid "Error determining boot device's disk name" msgstr "" -#: pyanaconda/iutil.py:871 +#: pyanaconda/iutil.py:888 msgid "the device containing /boot" msgstr "" -#: pyanaconda/iutil.py:873 +#: pyanaconda/iutil.py:890 #, python-format msgid "" "After shutdown, please perform a manual IPL from %s to continue installation." msgstr "" -#: pyanaconda/kickstart.py:153 +#: pyanaconda/kickstart.py:155 #, python-format msgid "Escrow certificate %s requires the network." msgstr "" -#: pyanaconda/kickstart.py:161 +#: pyanaconda/kickstart.py:163 #, python-format msgid "" "The following error was encountered while downloading the escrow " @@ -292,7 +288,7 @@ msgid "" "%s" msgstr "" -#: pyanaconda/kickstart.py:1502 +#: pyanaconda/kickstart.py:1521 msgid "Running pre-installation scripts" msgstr "" @@ -343,7 +339,7 @@ msgstr "" msgid "First sector of boot partition" msgstr "" -#: pyanaconda/platform.py:158 pyanaconda/platform.py:342 +#: pyanaconda/platform.py:158 pyanaconda/platform.py:341 msgid "Master Boot Record" msgstr "" @@ -430,7 +426,7 @@ msgid "" "\n" msgstr "" -#: pyanaconda/rescue.py:255 pyanaconda/storage/__init__.py:135 +#: pyanaconda/rescue.py:255 pyanaconda/storage/__init__.py:142 #: pyanaconda/storage/devicetree.py:95 msgid "Continue" msgstr "" @@ -751,14 +747,14 @@ msgid "_Eject" msgstr "" #: pyanaconda/yuminstall.py:922 pyanaconda/yuminstall.py:1285 -#: pyanaconda/yuminstall.py:1340 pyanaconda/yuminstall.py:1563 +#: pyanaconda/yuminstall.py:1340 pyanaconda/yuminstall.py:1568 msgid "_Retry" msgstr "" #: pyanaconda/yuminstall.py:925 pyanaconda/yuminstall.py:1012 #: pyanaconda/yuminstall.py:1017 pyanaconda/yuminstall.py:1290 -#: pyanaconda/yuminstall.py:1350 pyanaconda/yuminstall.py:1557 -#: pyanaconda/yuminstall.py:1579 pyanaconda/storage/zfcp.py:375 +#: pyanaconda/yuminstall.py:1350 pyanaconda/yuminstall.py:1562 +#: pyanaconda/yuminstall.py:1584 pyanaconda/storage/zfcp.py:375 msgid "Error" msgstr "" @@ -788,7 +784,7 @@ msgid "" msgstr "" #: pyanaconda/yuminstall.py:1019 pyanaconda/yuminstall.py:1114 -#: pyanaconda/yuminstall.py:1532 pyanaconda/yuminstall.py:1586 +#: pyanaconda/yuminstall.py:1537 pyanaconda/yuminstall.py:1591 msgid "_Back" msgstr "" @@ -867,12 +863,12 @@ msgstr "" msgid "Edit" msgstr "" -#: pyanaconda/yuminstall.py:1345 pyanaconda/yuminstall.py:1530 +#: pyanaconda/yuminstall.py:1345 pyanaconda/yuminstall.py:1535 #: pyanaconda/ui/gui/spokes/storage.glade:45 msgid "_Continue" msgstr "" -#: pyanaconda/yuminstall.py:1351 pyanaconda/yuminstall.py:1558 +#: pyanaconda/yuminstall.py:1351 pyanaconda/yuminstall.py:1563 #, python-format msgid "" "Unable to read package metadata. This may be due to a missing repodata " @@ -882,11 +878,11 @@ msgid "" "%s" msgstr "" -#: pyanaconda/yuminstall.py:1534 +#: pyanaconda/yuminstall.py:1539 msgid "Warning" msgstr "" -#: pyanaconda/yuminstall.py:1535 +#: pyanaconda/yuminstall.py:1540 msgid "" "Some of the packages you have selected for install are missing " "dependencies. You can exit the installation, go back and change your " @@ -895,7 +891,7 @@ msgid "" "missing components." msgstr "" -#: pyanaconda/yuminstall.py:1580 +#: pyanaconda/yuminstall.py:1585 #, python-format msgid "" "Your selected packages require %d MB of free space for installation, but you " @@ -903,48 +899,48 @@ msgid "" "installer." msgstr "" -#: pyanaconda/yuminstall.py:1665 +#: pyanaconda/yuminstall.py:1670 msgid "Post Upgrade" msgstr "" -#: pyanaconda/yuminstall.py:1666 +#: pyanaconda/yuminstall.py:1671 msgid "Performing post-upgrade configuration" msgstr "" -#: pyanaconda/yuminstall.py:1668 +#: pyanaconda/yuminstall.py:1673 msgid "Post Installation" msgstr "" -#: pyanaconda/yuminstall.py:1669 +#: pyanaconda/yuminstall.py:1674 msgid "Performing post-installation configuration" msgstr "" -#: pyanaconda/yuminstall.py:1828 +#: pyanaconda/yuminstall.py:1833 msgid "Installation Starting" msgstr "" -#: pyanaconda/yuminstall.py:1829 +#: pyanaconda/yuminstall.py:1834 msgid "Starting installation process" msgstr "" -#: pyanaconda/yuminstall.py:1867 +#: pyanaconda/yuminstall.py:1872 msgid "Dependency Check" msgstr "" -#: pyanaconda/yuminstall.py:1868 +#: pyanaconda/yuminstall.py:1873 msgid "Checking dependencies in packages selected for installation" msgstr "" -#: pyanaconda/yuminstall.py:1924 pyanaconda/yuminstall.py:1940 +#: pyanaconda/yuminstall.py:1929 pyanaconda/yuminstall.py:1945 msgid "Retrieving installation information." msgstr "" -#: pyanaconda/yuminstall.py:1926 pyanaconda/yuminstall.py:1942 +#: pyanaconda/yuminstall.py:1931 pyanaconda/yuminstall.py:1947 #, python-format msgid "Retrieving installation information for %s." msgstr "" -#: pyanaconda/yuminstall.py:1927 pyanaconda/yuminstall.py:1943 +#: pyanaconda/yuminstall.py:1932 pyanaconda/yuminstall.py:1948 msgid "Installation Progress" msgstr "" @@ -1003,149 +999,149 @@ msgstr "" msgid "Performing post-install setup tasks" msgstr "" -#: pyanaconda/storage/__init__.py:127 +#: pyanaconda/storage/__init__.py:134 msgid "Encrypt device?" msgstr "" -#: pyanaconda/storage/__init__.py:128 +#: pyanaconda/storage/__init__.py:135 msgid "" "You specified block device encryption should be enabled, but you have not " "supplied a passphrase. If you do not go back and provide a passphrase, block " "device encryption will be disabled." msgstr "" -#: pyanaconda/storage/__init__.py:135 pyanaconda/storage/devicetree.py:95 +#: pyanaconda/storage/__init__.py:142 pyanaconda/storage/devicetree.py:95 msgid "Back" msgstr "" -#: pyanaconda/storage/__init__.py:155 pyanaconda/storage/devicetree.py:104 +#: pyanaconda/storage/__init__.py:162 pyanaconda/storage/devicetree.py:104 msgid "Confirm" msgstr "" -#: pyanaconda/storage/__init__.py:156 +#: pyanaconda/storage/__init__.py:163 msgid "" "The partitioning options you have selected will now be written to disk. Any " "data on deleted or reformatted partitions will be lost." msgstr "" -#: pyanaconda/storage/__init__.py:161 +#: pyanaconda/storage/__init__.py:168 msgid "Go _Back" msgstr "" -#: pyanaconda/storage/__init__.py:162 +#: pyanaconda/storage/__init__.py:169 msgid "_Write Changes to Disk" msgstr "" -#: pyanaconda/storage/__init__.py:1027 +#: pyanaconda/storage/__init__.py:1009 msgid "This partition is holding the data for the hard drive install." msgstr "" -#: pyanaconda/storage/__init__.py:1032 +#: pyanaconda/storage/__init__.py:1014 msgid "You cannot delete a partition of a LDL formatted DASD." msgstr "" -#: pyanaconda/storage/__init__.py:1038 +#: pyanaconda/storage/__init__.py:1020 #, python-format msgid "This device is part of the RAID device %s." msgstr "" -#: pyanaconda/storage/__init__.py:1041 +#: pyanaconda/storage/__init__.py:1023 msgid "This device is part of a RAID device." msgstr "" -#: pyanaconda/storage/__init__.py:1044 +#: pyanaconda/storage/__init__.py:1026 msgid "This device is part of an inconsistent LVM Volume Group." msgstr "" -#: pyanaconda/storage/__init__.py:1049 +#: pyanaconda/storage/__init__.py:1031 #, python-format msgid "This device is part of the LVM volume group '%s'." msgstr "" -#: pyanaconda/storage/__init__.py:1052 +#: pyanaconda/storage/__init__.py:1034 msgid "This device is part of a LVM volume group." msgstr "" -#: pyanaconda/storage/__init__.py:1068 +#: pyanaconda/storage/__init__.py:1050 msgid "" "This device is an extended partition which contains logical partitions that " "cannot be deleted:\n" "\n" msgstr "" -#: pyanaconda/storage/__init__.py:1277 +#: pyanaconda/storage/__init__.py:1268 msgid "You must create a new filesystem on the root device." msgstr "" -#: pyanaconda/storage/__init__.py:1429 +#: pyanaconda/storage/__init__.py:1424 #, python-format msgid "" "You have not defined a root partition (/), which is required for " "installation of %s to continue." msgstr "" -#: pyanaconda/storage/__init__.py:1434 +#: pyanaconda/storage/__init__.py:1429 #, python-format msgid "" "Your root partition is less than 250 megabytes which is usually too small to " "install %s." msgstr "" -#: pyanaconda/storage/__init__.py:1448 +#: pyanaconda/storage/__init__.py:1443 msgid "" "This platform requires /boot on a dedicated partition or logical volume. If " "you do not want a /boot volume, you must place / on a dedicated non-LVM " "partition." msgstr "" -#: pyanaconda/storage/__init__.py:1459 +#: pyanaconda/storage/__init__.py:1454 #, python-format msgid "" "Your / partition does not match the the live image you are installing from. " "It must be formatted as %s." msgstr "" -#: pyanaconda/storage/__init__.py:1466 +#: pyanaconda/storage/__init__.py:1461 #, python-format msgid "" "Your %(mount)s partition is less than %(size)s megabytes which is lower than " "recommended for a normal %(productName)s install." msgstr "" -#: pyanaconda/storage/__init__.py:1476 +#: pyanaconda/storage/__init__.py:1471 #, python-format msgid "" "Your %(mount)s partition is too small for %(format)s formatting (allowable " "size is %(minSize)d MB to %(maxSize)d MB)" msgstr "" -#: pyanaconda/storage/__init__.py:1481 +#: pyanaconda/storage/__init__.py:1476 #, python-format msgid "" "Your %(mount)s partition is too large for %(format)s formatting (allowable " "size is %(minSize)d MB to %(maxSize)d MB)" msgstr "" -#: pyanaconda/storage/__init__.py:1508 +#: pyanaconda/storage/__init__.py:1503 msgid "" "Installing on a USB device. This may or may not produce a working system." msgstr "" -#: pyanaconda/storage/__init__.py:1511 +#: pyanaconda/storage/__init__.py:1506 msgid "" "Installing on a FireWire device. This may or may not produce a working " "system." msgstr "" -#: pyanaconda/storage/__init__.py:1517 +#: pyanaconda/storage/__init__.py:1512 msgid "you have not created a bootloader stage1 target device" msgstr "" -#: pyanaconda/storage/__init__.py:1526 +#: pyanaconda/storage/__init__.py:1521 msgid "You have not created a bootable partition." msgstr "" -#: pyanaconda/storage/__init__.py:1545 +#: pyanaconda/storage/__init__.py:1540 #, python-format msgid "" "Your BIOS-based system needs a special partition to boot with %s's new disk " @@ -1153,7 +1149,7 @@ msgid "" "partition." msgstr "" -#: pyanaconda/storage/__init__.py:1558 +#: pyanaconda/storage/__init__.py:1553 #, python-format msgid "" "You have not specified a swap partition. %(requiredMem)s MB of memory is " @@ -1161,13 +1157,13 @@ msgid "" "have %(installedMem)s MB." msgstr "" -#: pyanaconda/storage/__init__.py:1564 +#: pyanaconda/storage/__init__.py:1559 msgid "" "You have not specified a swap partition. Although not strictly required in " "all cases, it will significantly improve performance for most installations." msgstr "" -#: pyanaconda/storage/__init__.py:1570 +#: pyanaconda/storage/__init__.py:1565 msgid "" "At least one of your swap devices does not have a UUID, which is common in " "swap space created using older versions of mkswap. These devices will be " @@ -1175,13 +1171,13 @@ msgid "" "paths can change under a variety of circumstances. " msgstr "" -#: pyanaconda/storage/__init__.py:1580 +#: pyanaconda/storage/__init__.py:1575 #, python-format msgid "" "This mount point is invalid. The %s directory must be on the / file system." msgstr "" -#: pyanaconda/storage/__init__.py:1584 +#: pyanaconda/storage/__init__.py:1579 #, python-format msgid "The mount point %s must be on a linux file system." msgstr "" @@ -1214,17 +1210,17 @@ msgstr "" msgid "Migrating filesystem on %(device)s" msgstr "" -#: pyanaconda/storage/devicelibs/lvm.py:296 +#: pyanaconda/storage/devicelibs/lvm.py:311 #, python-format msgid "vginfo failed for %s" msgstr "" -#: pyanaconda/storage/devicelibs/lvm.py:325 +#: pyanaconda/storage/devicelibs/lvm.py:340 #, python-format msgid "lvs failed for %s" msgstr "" -#: pyanaconda/storage/devices.py:2724 +#: pyanaconda/storage/devices.py:2639 #, python-format msgid "A RAID%(raidLevel)d set requires at least %(minMembers)d member" msgid_plural "A RAID%(raidLevel)d set requires at least %(minMembers)d members" @@ -1271,33 +1267,33 @@ msgstr "" msgid "Unrecoverable Error" msgstr "" -#: pyanaconda/storage/formats/fs.py:889 +#: pyanaconda/storage/formats/fs.py:879 msgid "File system errors left uncorrected." msgstr "" -#: pyanaconda/storage/formats/fs.py:890 +#: pyanaconda/storage/formats/fs.py:880 msgid "Operational error." msgstr "" -#: pyanaconda/storage/formats/fs.py:891 +#: pyanaconda/storage/formats/fs.py:881 msgid "Usage or syntax error." msgstr "" -#: pyanaconda/storage/formats/fs.py:892 +#: pyanaconda/storage/formats/fs.py:882 msgid "e2fsck cancelled by user request." msgstr "" -#: pyanaconda/storage/formats/fs.py:893 +#: pyanaconda/storage/formats/fs.py:883 msgid "Shared library error." msgstr "" -#: pyanaconda/storage/formats/fs.py:1061 +#: pyanaconda/storage/formats/fs.py:1051 msgid "" "Recoverable errors have been detected or dosfsck has discovered an internal " "inconsistency." msgstr "" -#: pyanaconda/storage/formats/fs.py:1063 +#: pyanaconda/storage/formats/fs.py:1053 msgid "Usage error." msgstr "" @@ -1425,20 +1421,20 @@ msgstr "" msgid "Could not set zFCP device %(devnum)s offline (%(e)s)." msgstr "" -#: pyanaconda/ui/gui/__init__.py:98 +#: pyanaconda/ui/gui/__init__.py:113 #, python-format msgid "%(productName)s %(productVersion)s INSTALLATION" msgstr "" -#: pyanaconda/ui/gui/__init__.py:114 +#: pyanaconda/ui/gui/__init__.py:129 msgid "_Exit Installer" msgstr "" -#: pyanaconda/ui/gui/__init__.py:127 +#: pyanaconda/ui/gui/__init__.py:142 msgid "_No" msgstr "" -#: pyanaconda/ui/gui/__init__.py:127 +#: pyanaconda/ui/gui/__init__.py:142 msgid "_Yes" msgstr "" @@ -1470,76 +1466,76 @@ msgstr "" msgid "Complete!" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:56 +#: pyanaconda/ui/gui/spokes/custom.py:63 #, python-format msgid "New %s %s Installation" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:121 +#: pyanaconda/ui/gui/spokes/custom.py:141 msgid "MANUAL PARTITIONING" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:239 +#: pyanaconda/ui/gui/spokes/custom.py:348 #, python-format msgid "%d storage device selected" msgid_plural "%d storage devices selected" msgstr[0] "" msgstr[1] "" -#: pyanaconda/ui/gui/spokes/custom.py:320 +#: pyanaconda/ui/gui/spokes/custom.py:445 #: pyanaconda/ui/gui/spokes/network.py:554 msgid "Unknown" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:340 +#: pyanaconda/ui/gui/spokes/custom.py:465 msgid "" "The 'swap' area on your computer is used by the operating\n" "system when running low on memory." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:343 +#: pyanaconda/ui/gui/spokes/custom.py:468 msgid "" "The 'boot' area on your computer is where files needed\n" "to start the operating system are stored." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:346 +#: pyanaconda/ui/gui/spokes/custom.py:471 msgid "" "The 'root' area on your computer is where core system\n" "files and applications are stored." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:349 +#: pyanaconda/ui/gui/spokes/custom.py:474 msgid "" "The 'home' area on your computer is where all your personal\n" "data is stored." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:352 +#: pyanaconda/ui/gui/spokes/custom.py:477 msgid "No one knows what this could possibly be for." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:387 +#: pyanaconda/ui/gui/spokes/custom.py:513 msgid "This file system does not support labels." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:396 +#: pyanaconda/ui/gui/spokes/custom.py:523 msgid "This file system may not be resized." msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:633 +#: pyanaconda/ui/gui/spokes/custom.py:780 msgid "BTRFS" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:636 +#: pyanaconda/ui/gui/spokes/custom.py:783 msgid "LVM" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:639 +#: pyanaconda/ui/gui/spokes/custom.py:786 msgid "RAID" msgstr "" -#: pyanaconda/ui/gui/spokes/custom.py:642 +#: pyanaconda/ui/gui/spokes/custom.py:789 msgid "Standard Partition" msgstr "" @@ -1565,7 +1561,7 @@ msgstr "" msgid "You have no working NTP server configured" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.py:148 +#: pyanaconda/ui/gui/spokes/keyboard.py:149 msgid "KEYBOARD" msgstr "" @@ -1814,7 +1810,7 @@ msgid "Error setting up software source" msgstr "" #: pyanaconda/ui/gui/spokes/source.py:601 -#: pyanaconda/ui/gui/spokes/storage.py:404 +#: pyanaconda/ui/gui/spokes/storage.py:405 msgid "Probing storage..." msgstr "" @@ -1877,23 +1873,23 @@ msgstr "" msgid "INSTALLATION DESTINATION" msgstr "" -#: pyanaconda/ui/gui/spokes/storage.py:343 +#: pyanaconda/ui/gui/spokes/storage.py:344 msgid "No disks selected" msgstr "" -#: pyanaconda/ui/gui/spokes/storage.py:350 +#: pyanaconda/ui/gui/spokes/storage.py:351 msgid "Error checking storage configuration" msgstr "" -#: pyanaconda/ui/gui/spokes/storage.py:352 +#: pyanaconda/ui/gui/spokes/storage.py:353 msgid "Automatic partitioning selected" msgstr "" -#: pyanaconda/ui/gui/spokes/storage.py:354 +#: pyanaconda/ui/gui/spokes/storage.py:355 msgid "Custom partitioning selected" msgstr "" -#: pyanaconda/ui/gui/spokes/storage.py:465 +#: pyanaconda/ui/gui/spokes/storage.py:466 msgid "No disks selected; please select at least one disk to install to." msgstr "" @@ -1901,20 +1897,20 @@ msgstr "" msgid "LANGUAGE" msgstr "" -#: pyanaconda/ui/gui/spokes/lib/accordion.py:120 +#: pyanaconda/ui/gui/spokes/lib/accordion.py:119 msgid "DATA" msgstr "" -#: pyanaconda/ui/gui/spokes/lib/accordion.py:125 +#: pyanaconda/ui/gui/spokes/lib/accordion.py:124 msgid "SYSTEM" msgstr "" -#: pyanaconda/ui/gui/spokes/lib/accordion.py:208 +#: pyanaconda/ui/gui/spokes/lib/accordion.py:207 #, python-format msgid "You haven't created any mount points for your %s %s installation yet:" msgstr "" -#: pyanaconda/ui/gui/spokes/lib/accordion.py:223 +#: pyanaconda/ui/gui/spokes/lib/accordion.py:222 msgid "Or, create new mount points below with the '+' icon." msgstr "" @@ -2204,15 +2200,15 @@ msgstr "" msgid "_CONTINUE" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:76 +#: pyanaconda/ui/gui/spokes/keyboard.glade:78 msgid "ADD A KEYBOARD LAYOUT" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:91 +#: pyanaconda/ui/gui/spokes/keyboard.glade:93 msgid "You may add a keyboard layout by selecting it below:" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:120 +#: pyanaconda/ui/gui/spokes/keyboard.glade:122 msgid "Name" msgstr "" @@ -2226,15 +2222,15 @@ msgstr "" msgid "name" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:388 +#: pyanaconda/ui/gui/spokes/keyboard.glade:393 msgid "Test the selected layout below:" msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:414 +#: pyanaconda/ui/gui/spokes/keyboard.glade:426 msgid "Alt + Shift to switch layouts." msgstr "" -#: pyanaconda/ui/gui/spokes/keyboard.glade:427 +#: pyanaconda/ui/gui/spokes/keyboard.glade:439 msgid "_Options..." msgstr "" diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index ed8040ff5..04b4066d9 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -620,6 +620,7 @@ class BootLoader(object): return valid def set_stage1_device(self, devices): + self.stage1_device = None if not self.stage1_disk: raise BootLoaderError("need stage1 disk to set stage1 device") @@ -627,7 +628,6 @@ class BootLoader(object): self.stage1_device = self.stage2_device return - self.stage1_device = None for device in devices: if self.stage1_disk not in device.disks: continue @@ -1546,7 +1546,7 @@ class GRUB2(GRUB): self.stage2_device.format.sync(root=ROOT_PATH) class EFIGRUB(GRUB2): - packages = ["grub2-efi", "efibootmgr"] + packages = ["grub2-efi", "efibootmgr", "shim"] can_dual_boot = False @property @@ -1606,7 +1606,7 @@ class EFIGRUB(GRUB2): rc = self.efibootmgr("-c", "-w", "-L", productName, "-d", boot_disk.path, "-p", boot_part_num, "-l", - self.efi_dir_as_efifs_dir + "\\grubx64.efi", + self.efi_dir_as_efifs_dir + "\\shim.efi", root=ROOT_PATH, stdout="/dev/tty5", stderr="/dev/tty5") if rc: diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py index 1baf6bdd7..c2a4b2b13 100644 --- a/pyanaconda/constants.py +++ b/pyanaconda/constants.py @@ -49,6 +49,11 @@ productArch = product.productArch bugzillaUrl = product.bugUrl isFinal = product.isFinal +# for use in device names, eg: "fedora", "rhel" +shortProductName = productName.lower() +if productName.count(" "): + shortProductName = ''.join(s[0] for s in shortProductName.split()) + exceptionText = _("An unhandled exception has occurred. This " "is most likely a bug. Please save a copy of " "the detailed exception and file a bug report") diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py index cdef54215..8e519aaae 100644 --- a/pyanaconda/installclass.py +++ b/pyanaconda/installclass.py @@ -105,7 +105,7 @@ class BaseInstallClass(object): # provided as a way for other products to specify their own. return None - def setDefaultPartitioning(self, storage, platform): + def setDefaultPartitioning(self, storage): autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType, size=1024, maxSize=50*1024, grow=True, btr=True, lv=True, encrypted=True), @@ -113,14 +113,21 @@ class BaseInstallClass(object): size=500, grow=True, requiredSpace=50*1024, btr=True, lv=True, encrypted=True)] - bootreq = platform.setDefaultPartitioning() - if bootreq: - autorequests.extend(bootreq) + bootreqs = storage.platform.setDefaultPartitioning() + if bootreqs: + autorequests.extend(bootreqs) swp = swap.swapSuggestion() autorequests.append(PartSpec(fstype="swap", size=swp, grow=False, lv=True, encrypted=True)) + for autoreq in autorequests: + if autoreq.fstype is None: + if autoreq.mountpoint == "/boot": + autoreq.fstype = storage.defaultBootFSType + else: + autoreq.fstype = storage.defaultFSType + storage.autoPartitionRequests = autorequests def configure(self, anaconda): diff --git a/pyanaconda/installclasses/fedora.py b/pyanaconda/installclasses/fedora.py index e2c15433a..ae1abf015 100644 --- a/pyanaconda/installclasses/fedora.py +++ b/pyanaconda/installclasses/fedora.py @@ -76,9 +76,7 @@ class InstallClass(BaseInstallClass): def configure(self, anaconda): BaseInstallClass.configure(self, anaconda) - BaseInstallClass.setDefaultPartitioning(self, - anaconda.storage, - anaconda.platform) + BaseInstallClass.setDefaultPartitioning(self, anaconda.storage) def setGroupSelection(self, anaconda): BaseInstallClass.setGroupSelection(self, anaconda) diff --git a/pyanaconda/installclasses/rhel.py b/pyanaconda/installclasses/rhel.py index 25edbae41..765976649 100644 --- a/pyanaconda/installclasses/rhel.py +++ b/pyanaconda/installclasses/rhel.py @@ -55,9 +55,7 @@ class InstallClass(BaseInstallClass): def configure(self, anaconda): BaseInstallClass.configure(self, anaconda) - BaseInstallClass.setDefaultPartitioning(self, - anaconda.storage, - anaconda.platform) + BaseInstallClass.setDefaultPartitioning(self, anaconda.storage) def productMatches(self, oldprod): if oldprod is None: diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index 5c8c42c69..e3d7fd186 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -26,6 +26,7 @@ from storage.devicelibs.mpath import MultipathConfigWriter, MultipathTopology from storage.devicelibs import swap from storage.formats import getFormat from storage.partitioning import doPartitioning +from storage.partitioning import growLVM import storage.iscsi import storage.fcoe import storage.zfcp @@ -236,7 +237,6 @@ class Authconfig(commands.authconfig.FC3_Authconfig): class AutoPart(commands.autopart.F17_AutoPart): def execute(self, storage, ksdata, instClass): - from pyanaconda.platform import getPlatform from pyanaconda.storage.partitioning import doAutoPartition if not self.autopart: @@ -244,7 +244,7 @@ class AutoPart(commands.autopart.F17_AutoPart): # sets up default autopartitioning. use clearpart separately # if you want it - instClass.setDefaultPartitioning(storage, getPlatform()) + instClass.setDefaultPartitioning(storage) storage.doAutoPart = True if self.encrypted: @@ -539,6 +539,9 @@ class LogVol(commands.logvol.F17_LogVol): for l in self.lvList: l.execute(storage, ksdata, instClass) + if self.lvList: + growLVM(storage) + class LogVolData(commands.logvol.F17_LogVolData): def execute(self, storage, ksdata, instClass): devicetree = storage.devicetree @@ -845,6 +848,9 @@ class Partition(commands.partition.F17_Partition): for p in self.partitions: p.execute(storage, ksdata, instClass) + if self.partitions: + doPartitioning(storage) + class PartitionData(commands.partition.F17_PartData): def execute(self, storage, ksdata, instClass): devicetree = storage.devicetree @@ -1033,13 +1039,6 @@ class PartitionData(commands.partition.F17_PartData): except KeyError: pass - if "format" in kwargs: - # set weight based on fstype and mountpoint - mpt = getattr(kwargs["format"], "mountpoint", None) - fstype = kwargs["format"].type - kwargs["weight"] = storage.platform.weight(fstype=fstype, - mountpoint=mpt) - request = storage.newPartition(**kwargs) storage.createDevice(request) @@ -1600,5 +1599,4 @@ def doKickstartStorage(storage, ksdata, instClass): ksdata.logvol.execute(storage, ksdata, instClass) ksdata.btrfs.execute(storage, ksdata, instClass) storage.setUpBootLoader() - doPartitioning(storage) diff --git a/pyanaconda/platform.py b/pyanaconda/platform.py index 58cfa2dc4..6ec00374b 100644 --- a/pyanaconda/platform.py +++ b/pyanaconda/platform.py @@ -310,8 +310,6 @@ class S390(Platform): """Return the default platform-specific partitioning information.""" from storage.partspec import PartSpec return [PartSpec(mountpoint="/boot", size=500, -#TODO: need to pass this info in w/o using anaconda object -# fstype=self.anaconda.storage.defaultBootFSType, weight=self.weight(mountpoint="/boot"), lv=True, singlePV=True)] diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 914096234..70e772c36 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -26,6 +26,7 @@ import stat import errno import sys import statvfs +import copy import nss.nss import parted @@ -37,6 +38,7 @@ from pykickstart.constants import * from pyanaconda.flags import flags from pyanaconda import tsort from pyanaconda.errors import * +from pyanaconda.bootloader import BootLoaderError from errors import * from devices import * @@ -49,6 +51,11 @@ from devicelibs.dm import name_from_dm_node from devicelibs.crypto import generateBackupPassphrase from devicelibs.mpath import MultipathConfigWriter from devicelibs.edd import get_edd_dict +from devicelibs.mdraid import get_member_space +from devicelibs.lvm import get_pv_space +from .partitioning import SameSizeSet +from .partitioning import TotalSizeSet +from .partitioning import doPartitioning from udev import * import iscsi import fcoe @@ -296,31 +303,6 @@ class StorageDiscoveryConfig(object): self.initializeDisks = ksdata.clearpart.initAll self.zeroMbr = ksdata.zerombr.zerombr - def writeKS(self, f): - # clearpart - if self.clearPartType is None or self.clearPartType == CLEARPART_TYPE_NONE: - args = ["--none"] - elif self.clearPartType == CLEARPART_TYPE_LINUX: - args = ["--linux"] - else: - args = ["--all"] - - if self.clearPartDisks: - args += ["--drives=%s" % ",".join(self.clearPartDisks)] - if self.initializeDisks: - args += ["--initlabel"] - - f.write("#clearpart %s\n" % " ".join(args)) - - # ignoredisks - if self.ignoreDiskInteractive: - f.write("#ignoredisk --interactive\n") - elif self.ignoredDisks: - f.write("#ignoredisk --drives=%s\n" % ",".join(self.ignoredDisks)) - elif self.exclusiveDisks: - f.write("#ignoredisk --only-use=%s\n" % ",".join(self.exclusiveDisks)) - - class Storage(object): def __init__(self, data=None, platform=None): """ Create a Storage instance. @@ -348,10 +330,10 @@ class Storage(object): self.autoPartAddBackupPassphrase = False self.encryptionRetrofit = False self.autoPartitionRequests = [] - self.shrinkPartitions = {} self.eddDict = {} self.__luksDevs = {} + self.size_sets = [] self.iscsi = iscsi.iscsi() self.fcoe = fcoe.fcoe() @@ -1090,6 +1072,15 @@ class Storage(object): else: name = "req%d" % self.nextID + if "weight" not in kwargs: + fmt = kwargs.get("format") + if fmt: + mountpoint = getattr(fmt, "mountpoint", None) + + kwargs["weight"] = self.platform.weight(mountpoint=mountpoint, + fstype=fmt.type) + + return PartitionDevice(name, *args, **kwargs) def newMDArray(self, *args, **kwargs): @@ -1097,7 +1088,8 @@ class Storage(object): if kwargs.has_key("fmt_type"): kwargs["format"] = getFormat(kwargs.pop("fmt_type"), mountpoint=kwargs.pop("mountpoint", - None)) + None), + **kwargs.pop("fmt_args", {})) if kwargs.has_key("minor"): kwargs["minor"] = int(kwargs["minor"]) @@ -1128,7 +1120,7 @@ class Storage(object): hostname = nd.hostname break - name = self.suggestContainerName(hostname=hostname, prefix="vg") + name = self.suggestContainerName(hostname=hostname) if name in self.names: raise ValueError("name already in use") @@ -1141,7 +1133,8 @@ class Storage(object): mountpoint = kwargs.pop("mountpoint", None) if kwargs.has_key("fmt_type"): kwargs["format"] = getFormat(kwargs.pop("fmt_type"), - mountpoint=mountpoint) + mountpoint=mountpoint, + **kwargs.pop("fmt_args", {})) if kwargs.has_key("name"): name = kwargs.pop("name") @@ -1150,8 +1143,7 @@ class Storage(object): swap = True else: swap = False - name = self.suggestDeviceName(prefix="lv", - parent=vg, + name = self.suggestDeviceName(parent=vg, swap=swap, mountpoint=mountpoint) @@ -1168,7 +1160,9 @@ class Storage(object): name = args[0] mountpoint = kwargs.pop("mountpoint", None) - fmt_kwargs = {"mountpoint": mountpoint} + + fmt_args = kwargs.pop("fmt_args", {}) + fmt_args.update({"mountpoint": mountpoint}) if kwargs.pop("subvol", False): dev_class = BTRFSSubVolumeDevice @@ -1183,7 +1177,7 @@ class Storage(object): # for btrfs this only needs to ensure the subvol name is not # already in use within the parent volume name = self.suggestDeviceName(mountpoint=mountpoint) - fmt_kwargs["mountopts"] = "subvol=%s" % name + fmt_args["mountopts"] = "subvol=%s" % name kwargs.pop("metaDataLevel", None) kwargs.pop("dataLevel", None) else: @@ -1197,25 +1191,22 @@ class Storage(object): hostname = nd.hostname break - name = self.suggestContainerName(prefix="btrfs", - hostname=hostname) - #if name and name.startswith("btrfs_"): - # fmt_kwargs["label"] = name[6:] - #elif name and not name.startswith("btrfs"): - fmt_kwargs["label"] = name - - # do we want to prevent a subvol with a name that matches another dev? - if name in self.names: - raise ValueError("name already in use") + name = self.suggestContainerName(hostname=hostname) + if "label" not in fmt_args: + fmt_args["label"] = name # discard fmt_type since it's btrfs always kwargs.pop("fmt_type", None) # this is to avoid auto-scheduled format create actions device = dev_class(name, **kwargs) - device.format = getFormat("btrfs", **fmt_kwargs) + device.format = getFormat("btrfs", **fmt_args) return device + def newBTRFSSubVolume(self, *args, **kwargs): + kwargs["subvol"] = True + return self.newBTRFS(*args, **kwargs) + def createDevice(self, device): """ Schedule creation of a device. @@ -1306,15 +1297,19 @@ class Storage(object): def suggestContainerName(self, hostname=None, prefix=""): """ Return a reasonable, unused device name. """ + if not prefix: + prefix = shortProductName + # try to create a device name incorporating the hostname if hostname not in (None, "", 'localhost', 'localhost.localdomain'): template = "%s_%s" % (prefix, hostname.split('.')[0].lower()) template = self.safeDeviceName(template) - elif flags.imageInstall: - template = "%s_image" % prefix else: template = prefix + if flags.imageInstall: + template = "%s_image" % template + names = self.names name = template if name in names: @@ -1338,11 +1333,14 @@ class Storage(object): body = "" if mountpoint: if mountpoint == "/": - body = "_root" + body = "root" else: - body = mountpoint.replace("/", "_") + body = mountpoint[1:].replace("/", "_") elif swap: - body = "_swap" + body = "swap" + + if prefix: + body = "_" + body template = self.safeDeviceName(prefix + body) names = self.names @@ -1357,9 +1355,6 @@ class Storage(object): # also include names of any lvs in the parent for the case of the # temporary vg in the lvm dialogs, which can contain lvs that are # not yet in the devicetree and therefore not in self.names - if hasattr(parent, "lvs"): - names.extend([full_name(d.lvname, parent) for d in parent.lvs]) - if full_name(name, parent) in names or not body: for i in range(100): name = "%s%02d" % (template, i) @@ -1626,69 +1621,6 @@ class Storage(object): self.zfcp.write() self.dasd.write() - def writeKS(self, f): - def useExisting(lst): - foundCreateDevice = False - foundCreateFormat = False - - for l in lst: - if isinstance(l, ActionCreateDevice): - foundCreateDevice = True - elif isinstance(l, ActionCreateFormat): - foundCreateFormat = True - - return (foundCreateFormat and not foundCreateDevice) - - log.warning("Storage.writeKS not completely implemented") - f.write("# The following is the partition information you requested\n") - f.write("# Note that any partitions you deleted are not expressed\n") - f.write("# here so unless you clear all partitions first, this is\n") - f.write("# not guaranteed to work\n") - - self.config.writeKS(f) - - # the various partitioning commands - dict = {} - actions = filter(lambda x: x.device.format.type != "luks", - self.devicetree.findActions(type="create")) - - for action in actions: - if dict.has_key(action.device.path): - dict[action.device.path].append(action) - else: - dict[action.device.path] = [action] - - devices = self.devices - edges = [] - for dev in devices: - deps = [d for d in devices if dev.dependsOn(d)] - for dep in deps: - edges.append((devices.index(dep), devices.index(dev))) - - g = tsort.create_graph(range(len(devices)), edges) - order = tsort.tsort(g) - for idx in order: - device = devices[idx] - if device.format.type == "luks": - continue - - # If there's no action for the given device, it must be one - # we are reusing. - if not dict.has_key(device.path): - noformat = True - preexisting = True - else: - noformat = False - preexisting = useExisting(dict[device.path]) - - device.writeKS(f, preexisting=preexisting, noformat=noformat) - f.write("\n") - - self.iscsi.writeKS(f) - self.fcoe.writeKS(f) - self.zfcp.writeKS(f) - f.write("\n") - def turnOnSwap(self, upgrading=None): self.fsset.turnOnSwap(rootPath=ROOT_PATH, upgrading=upgrading) @@ -1732,7 +1664,10 @@ class Storage(object): self.bootloader.stage1_disk = self.devicetree.resolveDevice(self.data.bootloader.bootDrive) self.bootloader.stage2_device = self.bootDevice - self.bootloader.set_stage1_device(self.devices) + try: + self.bootloader.set_stage1_device(self.devices) + except BootLoaderError as e: + log.debug("failed to set bootloader stage1 device: %s" % e) @property def bootDisk(self): @@ -1750,6 +1685,10 @@ class Storage(object): return dev @property + def bootLoaderDevice(self): + return getattr(self.bootloader, "stage1_device", None) + + @property def bootFSTypes(self): """A list of all valid filesystem types for the boot partition.""" fstypes = [] @@ -1847,6 +1786,249 @@ class Storage(object): return 0 + def getFSType(self, mountpoint=None): + """ Return the default filesystem type based on mountpoint. """ + fstype = self.defaultFSType + if not mountpoint: + # just return the default + pass + elif mountpoint.lower() == "swap": + fstype = "swap" + elif mountpoint == "/boot": + fstype = self.defaultBootFSType + elif mountpoint == "/boot/efi": + if iutil.isMactel(): + fstype = "hfs+" + else: + fstype = "efi" + + return fstype + + def setContainerMembers(self, container, factory): + # set up member devices + container_size = 0 + if container: + log.debug("using container %s with %d devices" % (container.name, + len(self.devicetree.getChildren(container)))) + container_size = factory.container_size_func(container) + log.debug("raw container size reported as %d" % container_size) + disks = list(set([d for m in container.parents for d in m.disks])) + disks.sort(key=lambda d: d.name, cmp=self.compareDisks) + factory.disks = disks + + device_space = factory.device_size + log.debug("device requires %d" % device_space) + container_size += device_space + + # XXX TODO: multiple member devices per disk + + # XXX Never try to reuse member devices. They can be converted by the + # user into free space. + if container: + members = container.parents[:] + for member in members[:]: + if isinstance(member, LUKSDevice): + member = member.slave + + member.req_base_size = PartitionDevice.defaultSize + member.req_size = member.req_base_size + member.req_grow = True + + for ss in self.size_sets[:]: + if member in ss.devices: + self.size_sets.remove(ss) + else: + # set up members as needed to accommodate the device + members = [] + for disk in factory.disks: + if factory.encrypted: + luks_format = factory.member_format + member_format = "luks" + else: + member_format = factory.member_format + + member = self.newPartition(parents=[disk], grow=True, + fmt_type=member_format) + self.createDevice(member) + if factory.encrypted: + fmt = getFormat(luks_format) + member = LUKSDevice("luks-%s" % member.name, + parents=[member], format=fmt) + self.createDevice(member) + + members.append(member) + + log.debug("adding a %s with size %d" % (factory.set_class.__name__, + container_size)) + size_set = factory.set_class(members, container_size) + self.size_sets.append(size_set) + + try: + doPartitioning(self) + except StorageError as e: + log.error("UI: failed to allocate partitions: %s" % e) + for device in members: + actions = self.devicetree.findActions(device=device) + for a in reversed(actions): + self.devicetree.cancelAction(a) + # FIXME: revert container to its original state? + return + else: + # fix the sizes of all allocated partitions + for partition in self.partitions: + partition.req_grow = False + partition.req_base_size = partition.size + partition.req_size = partition.size + + return members + + def getDeviceFactory(self, device_type, size, **kwargs): + disks = kwargs.get("disks", []) + encrypted = kwargs.get("encrypted", False) + + # md, btrfs, lvm + raid_level = kwargs.get("level") + + class_table = {AUTOPART_TYPE_LVM: LVMFactory, + AUTOPART_TYPE_BTRFS: BTRFSFactory} + + factory_class = class_table.get(device_type, MDFactory) + log.debug("instantiating %s: %r, %s, %s, %s" % (factory_class, + self, size, [d.name for d in disks], raid_level)) + return factory_class(self, size, disks, raid_level, encrypted) + + def newDevice(self, device_type, size, **kwargs): + """ Schedule creation of a device based on a top-down specification. + + Arguments: + + device_type an AUTOPART_TYPE constant (lvm|btrfs|plain) + size device's requested size + + Keyword arguments: + + mountpoint the device's mountpoint + fstype the device's filesystem type, or swap + label filesystem label + disks the set of disks we can allocate from + encrypted boolean + + level (btrfs/md only) RAID level + + striped (lvm only) boolean + mirrored (lvm only) boolean + + """ + mountpoint = kwargs.get("mountpoint") + fstype = kwargs.get("fstype") + label = kwargs.get("label") + disks = kwargs.get("disks") + encrypted = kwargs.get("encrypted", self.data.autopart.encrypted) + + # md, btrfs + raid_level = kwargs.get("level") + + # lvm + striped = kwargs.get("striped") + mirrored = kwargs.get("mirrored") + + if not fstype: + fstype = self.getFSType(mountpoint=mountpoint) + if fstype == "swap": + mountpoint = None + + if fstype == "swap" and device_type == AUTOPART_TYPE_BTRFS: + device_type = AUTOPART_TYPE_PLAIN + elif device_type != AUTOPART_TYPE_PLAIN and \ + mountpoint and mountpoint.startswith("/boot"): + device_type = AUTOPART_TYPE_PLAIN + + fmt_args = {} + if label: + fmt_args["label"] = label + + # TODO: striping, mirroring, &c + # TODO: non-partition members (pv-on-md) + if device_type == AUTOPART_TYPE_PLAIN: + device = self.newPartition(grow=True, maxsize=size, + fmt_type=fstype, mountpoint=mountpoint, + fmt_args=fmt_args) + self.createDevice(device) + try: + doPartitioning(self) + except StorageError as e: + log.error("failed to allocate partitions: %s" % e) + actions = self.devicetree.findActions(device=device) + for a in reversed(actions): + self.devicetree.cancelAction(a) + else: + # fix the sizes of all allocated partitions + for partition in self.partitions: + partition.req_grow = False + partition.req_base_size = partition.size + partition.req_size = partition.size + + return + + factory = self.getDeviceFactory(device_type, size, **kwargs) + + for p in self.partitions: + log.debug("%r" % p) + + container = None + containers = [c for c in factory.container_list if not c.exists] + if containers: + container = containers[0] + + parents = self.setContainerMembers(container, factory) + + # set up container + if not container and factory.new_container_attr: + log.debug("creating new container") + container = factory.new_container(parents=parents) + self.createDevice(container) + + if container: + parents = [container] + log.debug("%r" % container) + + # add the new device to the container + if factory.new_device_attr: + free = getattr(container, "freeSpace", size) + if free < size: + log.info("adjusting device size from %.2f to %.2f so it fits " + "in container" % (size, free)) + size = free + + log.debug("creating new device") + device = factory.new_device(parents=parents, + size=size, + fmt_type=fstype, + mountpoint=mountpoint, + fmt_args=fmt_args) + self.createDevice(device) + + def copy(self): + new = copy.deepcopy(self) + # go through and re-get partedPartitions from the disks since they + # don't get deep-copied + for partition in new.partitions: + if not partition._partedPartition: + continue + + # don't ask me why, but we have to update the refs in req_disks + req_disks = [] + for disk in partition.req_disks: + req_disks.append(new.devicetree.getDeviceByID(disk.id)) + + partition.req_disks = req_disks + + p = partition.disk.format.partedDisk.getPartitionByPath(partition.path) + partition.partedPartition = p + + return new + + def mountExistingSystem(fsset, rootDevice, allowDirty=None, dirtyCB=None, readOnly=None): @@ -2788,3 +2970,112 @@ def parseFSTab(devicetree, chroot=None): return (mounts, swaps) + +class DeviceFactory(object): + type_desc = None + member_format = None + new_container_attr = None + new_device_attr = None + container_list_attr = None + + def __init__(self, storage, size, disks, raid_level, encrypted): + self.storage = storage + self.size = size + self.disks = disks + self.raid_level = raid_level + self.encrypted = encrypted + + self.size_func_kwargs = {} + + if raid_level in ("raid1", "raid10"): + self.set_class = SameSizeSet + else: + self.set_class = TotalSizeSet + + @property + def container_list(self): + return getattr(self.storage, self.container_list_attr) + + def new_container(self, *args, **kwargs): + return getattr(self.storage, self.new_container_attr)(*args, **kwargs) + + def new_device(self, *args, **kwargs): + return getattr(self.storage, self.new_device_attr)(*args, **kwargs) + + def container_size_func(self, container): + return container.size + + @property + def device_size(self): + return self.size + + +class BTRFSFactory(DeviceFactory): + type_desc = "btrfs" + member_format = "btrfs" + new_container_attr = "newBTRFS" + new_device_attr = "newBTRFSSubVolume" + container_list_attr = "btrfsVolumes" + + def __init__(self, storage, size, disks, raid_level, encrypted): + super(BTRFSFactory, self).__init__(storage, size, disks, raid_level, + encrypted) + self.raid_level = raid_level or "single" + + @property + def device_size(self): + # until we get/need something better + if self.raid_level in ("single", "raid0"): + return self.size + elif self.raid_level in ("raid1", "raid10"): + return self.size * len(self.disks) + + def container_size_func(self, container): + if container.exists: + size = container.size + else: + size = sum([s._size for s in container.subvolumes]) + + return size + +class LVMFactory(DeviceFactory): + type_desc = "lvm" + member_format = "lvmpv" + new_container_attr = "newVG" + new_device_attr = "newLV" + container_list_attr = "vgs" + + def __init__(self, storage, size, disks, raid_level, encrypted): + super(LVMFactory, self).__init__(storage, size, disks, raid_level, + encrypted) + if raid_level in ("raid1", "raid10"): + self.size_func_kwargs["mirrored"] = True + if raid_level in ("raid0", "raid10"): + self.size_func_kwargs["striped"] = True + + @property + def device_size(self): + return get_pv_space(self.size, len(self.disks), **self.size_func_kwargs) + + def container_size_func(self, container): + return container.size - container.freeSpace + +class MDFactory(DeviceFactory): + type_desc = "md" + member_format = "mdmember" + new_container_attr = None + new_device_attr = "newMD" + + def __init__(self, storage, size, disks, raid_level, encrypted): + super(MDFactory, self).__init__(storage, size, disks, raid_level, + encrypted) + self.size_func_kwargs = {"level": raid_level} + + @property + def container_list(self): + return [] + + @property + def device_size(self): + return get_member_space(self.size, len(self.disks), + **self.size_func_kwargs) diff --git a/pyanaconda/storage/devicelibs/btrfs.py b/pyanaconda/storage/devicelibs/btrfs.py index 3fe12188c..f114ba4ca 100644 --- a/pyanaconda/storage/devicelibs/btrfs.py +++ b/pyanaconda/storage/devicelibs/btrfs.py @@ -32,24 +32,21 @@ _ = lambda x: gettext.ldgettext("anaconda", x) import logging log = logging.getLogger("storage") -def btrfs(args, progress=None, capture=False): +def btrfs(args, capture=False): kwargs = {} kwargs["stderr"] = "/dev/tty5" if capture: - # execWithCapture can't take a progress window so it gets ignored exec_func = iutil.execWithCapture else: - exec_func = iutil.execWithPulseProgress + exec_func = iutil.execWithRedirect kwargs["stdout"] = "/dev/tty5" - kwargs["progress"] = progress ret = exec_func("btrfs", args, **kwargs) - if not capture and ret.rc: - raise BTRFSError(ret.stderr) - + if ret: + raise BTRFSError(ret) return ret -def create_volume(devices, label=None, data=None, metadata=None, progress=None): +def create_volume(devices, label=None, data=None, metadata=None): """ For now, data and metadata must be strings mkfs.btrfs understands. """ if not devices: raise ValueError("no devices specified") @@ -68,11 +65,10 @@ def create_volume(devices, label=None, data=None, metadata=None, progress=None): args.extend(devices) - ret = iutil.execWithPulseProgress("mkfs.btrfs", args, - stdout="/dev/tty5", stderr="/dev/tty5", - progress=progress) - if ret.rc: - raise BTRFSError(ret.stderr) + ret = iutil.execWithRedirect("mkfs.btrfs", args, + stdout="/dev/tty5", stderr="/dev/tty5") + if ret: + raise BTRFSError(ret) return ret @@ -82,21 +78,21 @@ def create_volume(devices, label=None, data=None, metadata=None, progress=None): # remove device -def create_subvolume(mountpoint, name, progress=None): +def create_subvolume(mountpoint, name): if not os.path.ismount(mountpoint): raise ValueError("volume not mounted") path = os.path.normpath("%s/%s" % (mountpoint, name)) args = ["subvol", "create", path] - return btrfs(args, progress=progress) + return btrfs(args) -def delete_subvolume(mountpoint, name, progress=None): +def delete_subvolume(mountpoint, name): if not os.path.ismount(mountpoint): raise ValueError("volume not mounted") path = os.path.normpath("%s/%s" % (mountpoint, name)) args = ["subvol", "delete", path] - return btrfs(args, progress=progress) + return btrfs(args) def create_snapshot(source, dest): pass diff --git a/pyanaconda/storage/devicelibs/lvm.py b/pyanaconda/storage/devicelibs/lvm.py index 136168b91..fedba99df 100644 --- a/pyanaconda/storage/devicelibs/lvm.py +++ b/pyanaconda/storage/devicelibs/lvm.py @@ -36,6 +36,10 @@ _ = lambda x: gettext.ldgettext("anaconda", x) MAX_LV_SLOTS = 256 +# some of lvm's defaults that we have no way to ask it for +LVM_PE_START = 1.0 # MB +LVM_PE_SIZE = 4.0 # MB + def has_lvm(): if iutil.find_program_in_path("lvm"): for line in open("/proc/devices").readlines(): @@ -143,6 +147,17 @@ def clampSize(size, pesize, roundup=None): return long(round(float(size)/float(pesize)) * pesize) +def get_pv_space(size, disks, pesize=LVM_PE_SIZE, + striped=False, mirrored=False): + """ Given specs for an LV, return total PV space required. """ + # XXX default extent size should be something we can ask of lvm + # TODO: handle striped and mirrored + # this is adding one extent for the lv's metadata + space = clampSize(size, pesize, roundup=True) + \ + (LVM_PE_START * disks) + \ + pesize + return space + def lvm(args): ret = iutil.execWithRedirect("lvm", args, stdout = "/dev/tty5", diff --git a/pyanaconda/storage/devicelibs/mdraid.py b/pyanaconda/storage/devicelibs/mdraid.py index e383bdf75..d0b85945d 100644 --- a/pyanaconda/storage/devicelibs/mdraid.py +++ b/pyanaconda/storage/devicelibs/mdraid.py @@ -31,6 +31,10 @@ _ = lambda x: gettext.ldgettext("anaconda", x) import logging log = logging.getLogger("storage") +# these defaults were determined empirically +MD_SUPERBLOCK_SIZE = 2.0 # MB +MD_CHUNK_SIZE = 0.5 # MB + # raidlevels constants RAID10 = 10 RAID6 = 6 @@ -124,6 +128,40 @@ def get_raid_max_spares(raidlevel, nummembers): raise ValueError, "invalid raid level %d" % raidlevel +def get_member_space(size, disks, level=None): + space = 0 # size of *each* member device + + if isinstance(level, str): + level = raidLevel(level) + + min_members = get_raid_min_members(level) + if disks < min_members: + raise ValueError("raid%d requires at least %d disks" + % (level, min_members)) + + if level == RAID0: + # you need the sum of the member sizes to equal your desired capacity + space = size / disks + elif level == RAID1: + # you need each member's size to equal your desired capacity + space = size + elif level in (RAID4, RAID5): + # you need the sum of all but one members' sizes to equal your desired + # capacity + space = size / (disks - 1) + elif level == RAID6: + # you need the sum of all but two members' sizes to equal your desired + # capacity + space = size / (disks - 2) + elif level == RAID10: + # you need the sum of the member sizes to equal twice your desired + # capacity + space = size / (disks / 2.0) + + space += MD_SUPERBLOCK_SIZE + + return space * disks + def mdadm(args): ret = iutil.execWithRedirect("mdadm", args, stdout = "/dev/tty5", diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py index 8fef3d168..650ced060 100644 --- a/pyanaconda/storage/devices.py +++ b/pyanaconda/storage/devices.py @@ -274,9 +274,6 @@ class Device(object): "parents": [p.name for p in self.parents]} return d - def writeKS(self, f, preexisting=False, noformat=False, s=None): - return - def removeChild(self): log_method_call(self, name=self.name, kids=self.kids) self.kids -= 1 @@ -1219,34 +1216,6 @@ class PartitionDevice(StorageDevice): "flags": self.partedPartition.getFlagsAsString()}) return d - def writeKS(self, f, preexisting=False, noformat=False, s=None): - args = [] - - if self.isExtended: - return - - if self.req_grow: - args.append("--grow") - if self.req_max_size: - args.append("--maxsize=%s" % self.req_max_size) - if self.req_primary: - args.append("--asprimary") - if self.req_size: - args.append("--size=%s" % (self.req_size or self.defaultSize)) - if preexisting: - if len(self.req_disks) == 1: - args.append("--ondisk=%s" % self.req_disks[0].name) - else: - args.append("--onpart=%s" % self.name) - if noformat: - args.append("--noformat") - - f.write("#part ") - self.format.writeKS(f) - f.write(" %s" % " ".join(args)) - if s: - f.write(" %s" % s) - def _setTargetSize(self, newsize): if newsize != self.currentSize: # change this partition's geometry in-memory so that other @@ -1925,13 +1894,6 @@ class LUKSDevice(DMCryptDevice): parents=parents, sysfsPath=sysfsPath, uuid=None, exists=exists) - def writeKS(self, f, preexisting=False, noformat=False, s=None): - self.slave.writeKS(f, preexisting=preexisting, noformat=noformat, s=s) - f.write(" ") - self.format.writeKS(f) - if s: - f.write(" %s" % s) - @property def size(self): if not self.exists or not self.partedDevice: @@ -2023,7 +1985,7 @@ class LVMVolumeGroupDevice(DMDevice): # TODO: validate peSize if given if not self.peSize: - self.peSize = 32.0 # MB + self.peSize = lvm.LVM_PE_SIZE # MB if not self.exists: self.pvCount = len(self.parents) @@ -2069,27 +2031,6 @@ class LVMVolumeGroupDevice(DMDevice): "lvNames": [lv.name for lv in self.lvs]}) return d - def writeKS(self, f, preexisting=False, noformat=False, s=None): - args = ["--pesize=%s" % int(self.peSize * 1024)] - pvs = [] - - for pv in self.pvs: - pvs.append("pv.%s" % pv.format.majorminor) - - if preexisting: - args.append("--useexisting") - if noformat: - args.append("--noformat") - - if self.reserved_space: - args.append("--reserved-space=%d" % self.reserved_space) - elif self.reserved_percent: - args.append("--reserved-percent=%d" % self.reserved_percent) - - f.write("#volgroup %s %s %s" % (self.name, " ".join(args), " ".join(pvs))) - if s: - f.write(" %s" % s) - @property def mapName(self): """ This device's device-mapper map name """ @@ -2492,32 +2433,6 @@ class LVMLogicalVolumeDevice(DMDevice): return d - def writeKS(self, f, preexisting=False, noformat=False, s=None): - args = ["--name=%s" % self.lvname, - "--vgname=%s" % self.vg.name] - - if self.req_grow: - args.extend(["--grow", "--size=%s" % (self.req_size or 1)]) - - if self.req_max_size > 0: - args.append("--maxsize=%s" % self.req_max_size) - else: - if self.req_percent > 0: - args.append("--percent=%s" % self.req_percent) - elif self.req_size > 0: - args.append("--size=%s" % self.req_size) - - if preexisting: - args.append("--useexisting") - if noformat: - args.append("--noformat") - - f.write("#logvol ") - self.format.writeKS(f) - f.write(" %s" % " ".join(args)) - if s: - f.write(" %s" % s) - @property def mirrored(self): return self.stripes > 1 @@ -2841,28 +2756,6 @@ class MDRaidArrayDevice(StorageDevice): "metadataVersion": self.metadataVersion}) return d - def writeKS(self, f, preexisting=False, noformat=False, s=None): - args = ["--level=%s" % self.level, - "--device=%s" % self.name] - mems = [] - - if self.spares > 0: - args.append("--spares=%s" % self.spares) - if preexisting: - args.append("--useexisting") - if noformat: - args.append("--noformat") - - for mem in self.parents: - mems.append("raid.%s" % mem.format.majorminor) - - f.write("#raid ") - self.format.writeKS(f) - f.write(" %s" % " ".join(args)) - f.write(" %s" % " ".join(mems)) - if s: - f.write(" %s" % s) - @property def mdadmConfEntry(self): """ This array's mdadm.conf entry. """ diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index df0d15f81..49fb1740e 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -1742,6 +1742,8 @@ class DeviceTree(object): self._devices.remove(device) self._hidden.append(device) lvm.lvm_cc_addFilterRejectRegexp(device.name) + for parent in device.parents: + parent.removeChild() def unhide(self, device): # the hidden list should be in leaves-first order @@ -1752,7 +1754,9 @@ class DeviceTree(object): hidden.id)) self._hidden.remove(hidden) self._devices.append(hidden) - lvm.lvm_cc_removeFilterRejectRegexp(device.name) + lvm.lvm_cc_removeFilterRejectRegexp(hidden.name) + for parent in device.parents: + parent.addChild() def _setupLvs(self): ret = False @@ -2102,6 +2106,11 @@ class DeviceTree(object): def getDevicesByInstance(self, device_class): return [d for d in self._devices if isinstance(d, device_class)] + def getDeviceByID(self, id_num): + for device in self._devices: + if device.id == id_num: + return device + @property def devices(self): """ List of device instances """ diff --git a/pyanaconda/storage/fcoe.py b/pyanaconda/storage/fcoe.py index fc2ad4017..d0614756a 100644 --- a/pyanaconda/storage/fcoe.py +++ b/pyanaconda/storage/fcoe.py @@ -141,10 +141,6 @@ class fcoe(object): self._stabilize() self.nics.append((nic, dcb, auto_vlan)) - def writeKS(self, f): - # fixme plenty (including add ks support for fcoe in general) - return - def write(self): if not self.nics: return diff --git a/pyanaconda/storage/formats/__init__.py b/pyanaconda/storage/formats/__init__.py index 3df470ebf..12ca5bf1c 100644 --- a/pyanaconda/storage/formats/__init__.py +++ b/pyanaconda/storage/formats/__init__.py @@ -437,7 +437,4 @@ class DeviceFormat(object): (udev_device_get_major(dev), udev_device_get_minor(dev)) return self._majorminor - def writeKS(self, f): - return - collect_device_format_classes() diff --git a/pyanaconda/storage/formats/biosboot.py b/pyanaconda/storage/formats/biosboot.py index 34bd76019..861b59190 100644 --- a/pyanaconda/storage/formats/biosboot.py +++ b/pyanaconda/storage/formats/biosboot.py @@ -56,9 +56,5 @@ class BIOSBoot(DeviceFormat): from pyanaconda import platform return isinstance(platform.getPlatform(), platform.X86) - def writeKS(self, f): - f.write("biosboot --fstype=%s" % self.type) - - register_device_format(BIOSBoot) diff --git a/pyanaconda/storage/formats/disklabel.py b/pyanaconda/storage/formats/disklabel.py index 338a88011..87dbea2d8 100644 --- a/pyanaconda/storage/formats/disklabel.py +++ b/pyanaconda/storage/formats/disklabel.py @@ -85,10 +85,13 @@ class DiskLabel(DeviceFormat): """ new = self.__class__.__new__(self.__class__) memo[id(self)] = new - shallow_copy_attrs = ('_partedDevice', '_partedDisk', '_origPartedDisk') + shallow_copy_attrs = ('_partedDevice', '_alignment', '_endAlignment') + duplicate_attrs = ('_partedDisk', '_origPartedDisk') for (attr, value) in self.__dict__.items(): if attr in shallow_copy_attrs: setattr(new, attr, copy.copy(value)) + elif attr in duplicate_attrs: + setattr(new, attr, value.duplicate()) else: setattr(new, attr, copy.deepcopy(value, memo)) diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py index eaf2405d3..b9cf3b370 100644 --- a/pyanaconda/storage/formats/fs.py +++ b/pyanaconda/storage/formats/fs.py @@ -868,16 +868,6 @@ class FS(DeviceFormat): def sync(self, root="/"): pass - def writeKS(self, f): - f.write("%s --fstype=%s" % (self.mountpoint, self.type)) - - if self.label: - f.write(" --label=\"%s\"" % self.label) - - if self.fsprofile: - f.write(" --fsprofile=\"%s\"" % self.fsprofile) - - class Ext2FS(FS): """ ext2 filesystem. """ _type = "ext2" @@ -1332,9 +1322,6 @@ class AppleBootstrapFS(HFS): return (isinstance(platform.getPlatform(), platform.NewWorldPPC) and self.utilsAvailable) - def writeKS(self, f): - f.write("appleboot --fstype=%s" % self.type) - register_device_format(AppleBootstrapFS) @@ -1471,9 +1458,6 @@ class Iso9660FS(FS): _migratable = False _defaultMountOptions = ["ro"] - def writeKS(self, f): - return - register_device_format(Iso9660FS) @@ -1492,9 +1476,6 @@ class NoDevFS(FS): def _getExistingSize(self): pass - def writeKS(self, f): - return - register_device_format(NoDevFS) @@ -1535,9 +1516,6 @@ class BindFS(FS): def _getExistingSize(self): pass - def writeKS(self, f): - return - register_device_format(BindFS) diff --git a/pyanaconda/storage/formats/luks.py b/pyanaconda/storage/formats/luks.py index 8ddad642e..6bb8b6486 100644 --- a/pyanaconda/storage/formats/luks.py +++ b/pyanaconda/storage/formats/luks.py @@ -107,9 +107,6 @@ class LUKS(DeviceFormat): "backup": self.add_backup_passphrase}) return s - def writeKS(self, f): - f.write(" --encrypted") - @property def dict(self): d = super(LUKS, self).dict diff --git a/pyanaconda/storage/formats/lvmpv.py b/pyanaconda/storage/formats/lvmpv.py index 994561e68..77bbacdbd 100644 --- a/pyanaconda/storage/formats/lvmpv.py +++ b/pyanaconda/storage/formats/lvmpv.py @@ -65,7 +65,7 @@ class LVMPhysicalVolume(DeviceFormat): self.vgUuid = kwargs.get("vgUuid") # liblvm may be able to tell us this at some point, even # for not-yet-created devices - self.peStart = kwargs.get("peStart", 1.0) # in MB + self.peStart = kwargs.get("peStart", lvm.LVM_PE_START) # in MB self.inconsistentVG = False @@ -141,8 +141,5 @@ class LVMPhysicalVolume(DeviceFormat): return (self.exists and self.vgName and os.path.isdir("/dev/mapper/%s" % self.vgName)) - def writeKS(self, f): - f.write("pv.%s" % self.majorminor) - register_device_format(LVMPhysicalVolume) diff --git a/pyanaconda/storage/formats/mdraid.py b/pyanaconda/storage/formats/mdraid.py index 39164a4ee..c543434cf 100644 --- a/pyanaconda/storage/formats/mdraid.py +++ b/pyanaconda/storage/formats/mdraid.py @@ -110,9 +110,6 @@ class MDRaidMember(DeviceFormat): def hidden(self): return (self._hidden or self.biosraid) - def writeKS(self, f): - f.write("raid.%s" % self.majorminor) - # nodmraid -> Wether to use BIOS RAID or not # Note the anaconda cmdline has not been parsed yet when we're first imported, # so we can not use flags.dmraid here diff --git a/pyanaconda/storage/formats/prepboot.py b/pyanaconda/storage/formats/prepboot.py index f671e9563..884aa8fc4 100644 --- a/pyanaconda/storage/formats/prepboot.py +++ b/pyanaconda/storage/formats/prepboot.py @@ -83,9 +83,5 @@ class PPCPRePBoot(DeviceFormat): from pyanaconda import platform return isinstance(platform.getPlatform(), platform.IPSeriesPPC) - def writeKS(self, f): - f.write("prepboot --fstype=%s" % self.type) - - register_device_format(PPCPRePBoot) diff --git a/pyanaconda/storage/formats/swap.py b/pyanaconda/storage/formats/swap.py index e961afe49..2267d680d 100644 --- a/pyanaconda/storage/formats/swap.py +++ b/pyanaconda/storage/formats/swap.py @@ -167,12 +167,5 @@ class SwapSpace(DeviceFormat): self.exists = True self.notifyKernel() - def writeKS(self, f): - f.write("swap") - - if self.label: - f.write(" --label=\"%s\"" % self.label) - - register_device_format(SwapSpace) diff --git a/pyanaconda/storage/iscsi.py b/pyanaconda/storage/iscsi.py index ce21a6d0f..3598ae05b 100644 --- a/pyanaconda/storage/iscsi.py +++ b/pyanaconda/storage/iscsi.py @@ -395,32 +395,6 @@ class iscsi(object): self.stabilize(intf) - def writeKS(self, f): - - if not self.initiatorSet: - return - - nodes = "" - for n in self.active_nodes(): - if n in self.ibftNodes: - continue - nodes += "iscsi --ipaddr %s --port %s --target %s" % (n.address, n.port, n.name) - if n.iface != "default": - nodes += " --iface %s" % self.ifaces[n.iface] - auth = n.getAuth() - if auth: - nodes += " --user %s" % auth.username - nodes += " --password %s" % auth.password - if len(auth.reverse_username): - nodes += " --reverse-user %s" % auth.reverse_username - if len(auth.reverse_password): - nodes += " --reverse-password %s" % auth.reverse_password - nodes += "\n" - - if nodes: - f.write("iscsiname %s\n" %(self.initiator,)) - f.write("%s" % nodes) - def write(self, storage): if not self.initiatorSet: return diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index e9c06f6d0..7f34b85df 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -131,12 +131,6 @@ def _schedulePartitions(storage, disks): if request.requiredSpace and request.requiredSpace > free: continue - if request.fstype is None: - if request.mountpoint == "/boot": - request.fstype = storage.defaultBootFSType - else: - request.fstype = storage.defaultFSType - elif request.fstype in ("prepboot", "efi", "biosboot", "hfs+") and \ stage1_device: # there should never be a need for more than one of these @@ -263,6 +257,7 @@ def _scheduleVolumes(storage, devs): "singlePV": request.singlePV}) else: kwargs.update({"parents": [container], + "size": request.size, "subvol": True}) dev = new_volume(**kwargs) @@ -270,19 +265,6 @@ def _scheduleVolumes(storage, devs): # schedule the device for creation storage.createDevice(dev) -def scheduleShrinkActions(storage): - """ Schedule actions to shrink partitions as per autopart selection. """ - for (path, size) in storage.shrinkPartitions.items(): - device = storage.devicetree.getDeviceByPath(path, preferLeaves=False) - if not device or not isinstance(device, PartitionDevice): - raise StorageError("device %s scheduled for shrink disappeared" - % path) - storage.devicetree.registerAction(ActionResizeFormat(device, size)) - storage.devicetree.registerAction(ActionResizeDevice(device, size)) - # aligning the partition's new end sector may have changed the size - if device.targetSize != size: - device.format.targetSize = device.targetSize - def doAutoPartition(storage, data): log.debug("doAutoPart: %s" % storage.doAutoPart) log.debug("encryptedAutoPart: %s" % storage.encryptedAutoPart) @@ -299,9 +281,6 @@ def doAutoPartition(storage, data): devs = [] if storage.doAutoPart: - # XXX this doesn't belong on newui - scheduleShrinkActions(storage) - disks = _getCandidateDisks(storage) devs = _scheduleImplicitPartitions(storage, disks) log.debug("candidate disks: %s" % disks) @@ -321,6 +300,8 @@ def doAutoPartition(storage, data): # grow LVs growLVM(storage) + + growBTRFS(storage) except PartitioningWarning as e: log.warning(str(e)) if errorHandler.cb(e) == ERROR_RAISE: @@ -768,8 +749,7 @@ def doPartitioning(storage): for part in storage.partitions: part.req_bootable = False - if part.exists or \ - (storage.deviceImmutable(part) and part.partedPartition): + if part.exists: # if the partition is preexisting or part of a complex device # then we shouldn't modify it partitions.remove(part) @@ -789,7 +769,7 @@ def doPartitioning(storage): free = getFreeRegions(disks) try: allocatePartitions(storage, disks, partitions, free) - growPartitions(disks, partitions, free) + growPartitions(disks, partitions, free, size_sets=storage.size_sets) finally: # The number and thus the name of partitions may have changed now, # allocatePartitions() takes care of this for new partitions, but not @@ -1081,13 +1061,14 @@ class Request(object): self.device = device self.growth = 0 # growth in sectors self.max_growth = 0 # max growth in sectors - self.done = not device.req_grow # can we grow this request more? + self.done = not getattr(device, "req_grow", True) # can we grow this + # request more? self.base = 0 # base sectors @property def growable(self): """ True if this request is growable. """ - return self.device.req_grow + return getattr(self.device, "req_grow", True) @property def id(self): @@ -1188,6 +1169,8 @@ class Chunk(object): for req in requests: self.addRequest(req) + self.skip_list = [] + def __repr__(self): s = ("%(type)s instance --\n" "device = %(device)s length = %(length)d size = %(size)d\n" @@ -1212,6 +1195,22 @@ class Chunk(object): if not req.done: self.base += req.base + def reclaim(self, request, amount): + """ Reclaim units from a request and return them to the pool. """ + log.debug("reclaim: %s %d (%d MB)" % (request, amount, self.lengthToSize(amount))) + if request.growth < amount: + log.error("tried to reclaim %d from request with %d of growth" + % (amount, request.growth)) + raise ValueError("cannot reclaim more than request has grown") + + request.growth -= amount + self.pool += amount + + # put this request in the skip list so we don't try to grow it the + # next time we call growRequests to allocate the newly re-acquired pool + if request not in self.skip_list: + self.skip_list.append(request) + @property def growth(self): """ Sum of growth for all requests in this chunk. """ @@ -1291,7 +1290,7 @@ class Chunk(object): log.debug("%d requests and %d (%dMB) left in chunk" % (self.remaining, self.pool, self.lengthToSize(self.pool))) for p in self.requests: - if p.done: + if p.done or p in self.skip_list: continue if not uniform: @@ -1336,6 +1335,10 @@ class Chunk(object): if self.pool == 0: break + # requests that were skipped over this time through are back on the + # table next time + self.skip_list = [] + class DiskChunk(Chunk): """ A free region on disk from which partitions will be allocated """ @@ -1549,7 +1552,121 @@ def getDiskChunks(disk, partitions, free): return chunks -def growPartitions(disks, partitions, free): +class TotalSizeSet(object): + """ Set of device requests with a target combined size. + + This will be handled by growing the requests until the desired combined + size has been achieved. + """ + def __init__(self, devices, size): + self.devices = devices + self.size = size + + self.requests = [] + + self.allocated = sum([d.req_base_size for d in self.devices]) + log.debug("set.allocated = %d" % self.allocated) + + def allocate(self, amount): + log.debug("allocating %d to TotalSizeSet with %d/%d (%d needed)" + % (amount, self.allocated, self.size, self.needed)) + self.allocated += amount + + @property + def needed(self): + return self.size - self.allocated + + def deallocate(self, amount): + log.debug("deallocating %d from TotalSizeSet with %d/%d (%d needed)" + % (amount, self.allocated, self.size, self.needed)) + self.allocated -= amount + +class SameSizeSet(object): + """ Set of device requests with a common target size. """ + def __init__(self, devices, size, grow=False, max_size=None): + self.devices = devices + self.size = int(size / len(devices)) + self.grow = grow + self.max_size = max_size + + self.requests = [] + +def manageSizeSets(size_sets, chunks): + growth_by_request = {} + requests_by_device = {} + chunks_by_request = {} + for chunk in chunks: + for request in chunk.requests: + requests_by_device[request.device] = request + chunks_by_request[request] = chunk + growth_by_request[request] = 0 + + for i in range(2): + reclaimed = dict([(chunk, 0) for chunk in chunks]) + for ss in size_sets: + if isinstance(ss, TotalSizeSet): + # TotalSizeSet members are trimmed to achieve the requested + # total size + log.debug("set: %s %d/%d" % ([d.name for d in ss.devices], + ss.allocated, ss.size)) + + for device in ss.devices: + request = requests_by_device[device] + if request.done: + continue + + chunk = chunks_by_request[request] + new_growth = request.growth - growth_by_request[request] + ss.allocate(chunk.lengthToSize(new_growth)) + + # decide how much to take back from each request + # We may assume that all requests have the same base size. + # We're shooting for a roughly equal distribution by trimming + # growth from the requests that have grown the most first. + requests = sorted([requests_by_device[d] for d in ss.devices], + key=lambda r: r.growth, reverse=True) + for request in requests: + chunk = chunks_by_request[request] + log.debug("%s" % request) + log.debug("needed: %d" % ss.needed) + + if ss.needed < 0: + # it would be good to take back some from each device + # instead of taking all from the last one(s) + extra = min(-chunk.sizeToLength(ss.needed), + request.growth) + reclaimed[chunk] += extra + chunk.reclaim(request, extra) + ss.deallocate(chunk.lengthToSize(extra)) + + if ss.needed <= 0: + request.done = True + + elif isinstance(ss, SameSizeSet): + # SameSizeSet members all have the same size as the smallest + # member + requests = [requests_by_device[d] for d in ss.devices] + min_growth = min([r.growth for r in requests]) + for request in requests: + if request.growth > min_growth: + chunk = chunks_by_request[request] + extra = request.growth - min_growth + reclaimed[chunk] += extra + chunk.reclaim(request, extra) + request.done = True + elif request.growth == min_growth: + request.done = True + + # store previous growth amounts so we know how much was allocated in + # the latest growRequests call + for request in growth_by_request.keys(): + growth_by_request[request] = request.growth + + for chunk in chunks: + if reclaimed[chunk] and not chunk.done: + chunk.growRequests() + +def growPartitions(disks, partitions, free, size_sets=None): """ Grow all growable partition requests. Partitions have already been allocated from chunks of free space on @@ -1575,36 +1692,60 @@ def growPartitions(disks, partitions, free): log.debug("no growable partitions") return + if size_sets is None: + size_sets = [] + log.debug("growable partitions are %s" % [p.name for p in all_growable]) + # + # collect info about each disk and the requests it contains + # + chunks = [] for disk in disks: - log.debug("growing partitions on %s" % disk.name) sector_size = disk.format.partedDevice.sectorSize - # find any extended partition on this disk - extended_geometry = getattr(disk.format.extendedPartition, - "geometry", - None) # parted.Geometry - # list of free space regions on this disk prior to partition allocation disk_free = [f for f in free if f.device.path == disk.path] if not disk_free: log.debug("no free space on %s" % disk.name) continue - chunks = getDiskChunks(disk, partitions, disk_free) - log.debug("disk %s has %d chunks" % (disk.name, len(chunks))) - # grow the partitions in each chunk as a group + disk_chunks = getDiskChunks(disk, partitions, disk_free) + log.debug("disk %s has %d chunks" % (disk.name, len(disk_chunks))) + chunks.extend(disk_chunks) + + # + # grow the partitions in each chunk as a group + # + for chunk in chunks: + if not chunk.hasGrowable: + # no growable partitions in this chunk + continue + + chunk.growRequests() + + # adjust set members' growth amounts as needed + manageSizeSets(size_sets, chunks) + + for disk in disks: + log.debug("growing partitions on %s" % disk.name) for chunk in chunks: + if chunk.path != disk.path: + continue + if not chunk.hasGrowable: # no growable partitions in this chunk continue - chunk.growRequests() - # recalculate partition geometries disklabel = disk.format start = chunk.geometry.start + + # find any extended partition on this disk + extended_geometry = getattr(disklabel.extendedPartition, + "geometry", + None) # parted.Geometry + # align start sector as needed if not disklabel.alignment.isAligned(chunk.geometry, start): start = disklabel.alignment.alignUp(chunk.geometry, start) @@ -1759,3 +1900,21 @@ def growLVM(storage): # initial size. req.device.size = chunk.lengthToSize(req.base + req.growth) +def growBTRFS(storage): + """ Set up relative sizes for subvolumes after autopart for custom ui. """ + for vol in storage.btrfsVolumes: + if vol.exists: + continue + + requests = [] + for subvol in vol.subvolumes: + request = Request(subvol) + request.base = subvol._size + requests.append(request) + + chunk = Chunk(int(vol.size), requests=requests) + chunk.path = "btrfs %s" % vol.name + chunk.growRequests() + + for req in chunk.requests: + req.device._size = chunk.lengthToSize(req.base + req.growth) diff --git a/pyanaconda/storage/size.py b/pyanaconda/storage/size.py index eb3e2e4ff..75ea933e0 100644 --- a/pyanaconda/storage/size.py +++ b/pyanaconda/storage/size.py @@ -139,27 +139,29 @@ class Size(Decimal): return self - def __str__(self): + def __str__(self, context=None): return self.humanReadable() def __repr__(self): return "Size('%s')" % self - def __add__(self, other): - return Size(bytes=Decimal.__add__(self, other)) + def __add__(self, other, context=None): + return Size(bytes=Decimal.__add__(self, other, context=context)) # needed to make sum() work with Size arguments - def __radd__(self, other): - return Size(bytes=Decimal.__radd__(self, other)) + def __radd__(self, other, context=None): + return Size(bytes=Decimal.__radd__(self, other, context=context)) - def __sub__(self, other): - return Size(bytes=Decimal.__sub__(self, other)) + def __sub__(self, other, context=None): + # subtraction is implemented using __add__ and negation, so we'll + # be getting passed a Size + return Decimal.__sub__(self, other, context=context) - def __mul__(self, other): - return Size(bytes=Decimal.__mul__(self, other)) + def __mul__(self, other, context=None): + return Size(bytes=Decimal.__mul__(self, other, context=context)) - def __div__(self, other): - return Size(bytes=Decimal.__div__(self, other)) + def __div__(self, other, context=None): + return Size(bytes=Decimal.__div__(self, other, context=context)) def _trimEnd(self, val): """ Internal method to trim trailing zeros. """ diff --git a/pyanaconda/storage/zfcp.py b/pyanaconda/storage/zfcp.py index e74c1e10e..3612dc07b 100644 --- a/pyanaconda/storage/zfcp.py +++ b/pyanaconda/storage/zfcp.py @@ -411,14 +411,6 @@ class ZFCP: except ValueError as e: log.warn(str(e)) - def writeKS(self, f): - if len(self.fcpdevs) == 0: - return - for d in self.fcpdevs: - f.write("zfcp --devnum %s --wwpn %s --fcplun %s\n" %(d.devnum, - d.wwpn, - d.fcplun)) - def write(self): if len(self.fcpdevs) == 0: return diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py index 10570a8cc..02f95e4ed 100644 --- a/pyanaconda/ui/gui/__init__.py +++ b/pyanaconda/ui/gui/__init__.py @@ -18,7 +18,7 @@ # # Red Hat Author(s): Chris Lumens <clumens@redhat.com> # -import importlib, inspect, os, sys +import importlib, inspect, os, sys, time import meh.ui.gui from pyanaconda.ui import UserInterface, common @@ -27,6 +27,9 @@ from pyanaconda.ui.gui.utils import enlightbox import gettext _ = lambda x: gettext.ldgettext("anaconda", x) +import logging +log = logging.getLogger("anaconda") + __all__ = ["GraphicalUserInterface", "UIObject"] _screenshotIndex = 0 @@ -79,6 +82,18 @@ class GraphicalUserInterface(UserInterface): def run(self): from gi.repository import Gtk + if Gtk.main_level() > 0: + # Gtk main loop running. That means python-meh caught exception + # and runs its main loop. Do not crash Gtk by running another one + # from a different thread and just wait until python-meh is + # finished, then quit. + log.error("Unhandled exception caught, waiting for python-meh to "\ + "exit") + while Gtk.main_level() > 0: + time.sleep(2) + + sys.exit(0) + from pyanaconda.product import isFinal, productName, productVersion # If we set these values on the very first window shown, they will get diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index c76146a72..9f3a72b07 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -32,13 +32,17 @@ _ = lambda x: gettext.ldgettext("anaconda", x) N_ = lambda x: x P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z) +from contextlib import contextmanager + from pykickstart.constants import * from pyanaconda.product import productName, productVersion from pyanaconda.storage.formats import device_formats +from pyanaconda.storage.formats import getFormat from pyanaconda.storage.size import Size from pyanaconda.storage import Root from pyanaconda.storage.partitioning import doPartitioning +from pyanaconda.storage.partitioning import doAutoPartition from pyanaconda.storage.errors import StorageError from pyanaconda.ui.gui import GUIObject @@ -51,10 +55,26 @@ from pyanaconda.ui.gui.categories.storage import StorageCategory from gi.repository import Gtk +import logging +log = logging.getLogger("anaconda") + __all__ = ["CustomPartitioningSpoke"] new_install_name = _("New %s %s Installation") % (productName, productVersion) +class UIStorageFilter(logging.Filter): + def filter(self, record): + record.name = "storage.ui" + return True + +@contextmanager +def ui_storage_logger(): + storage_log = logging.getLogger("storage") + f = UIStorageFilter() + storage_log.addFilter(f) + yield + storage_log.removeFilter(f) + class AddDialog(GUIObject): builderObjects = ["addDialog"] mainWidgetName = "addDialog" @@ -125,8 +145,98 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._current_selector = None self._when_create_text = "" + self._devices = [] + self._unused_devices = None # None indicates uninitialized + self._free_space = Size(bytes=0) + + def _propagate_actions(self, actions): + """ Register actions from the UI with the main Storage instance. """ + for ui_action in actions: + ui_device = ui_action.device + device = self.storage.devicetree.getDeviceByID(ui_device.id) + if device is None: + # This device does not exist in the main devicetree. + # + # That means it was created in the ui. If it is still present in + # the ui device list, schedule a create action for it. If not, + # just ignore it. + if not ui_action.isCreate or ui_device not in self._devices: + # this is a device that was created and then destroyed here + continue + + args = [device] + if ui_action.isResize: + args.append(ui_device.targetSize) + + if ui_action.isCreate and ui_action.isFormat: + args.append(ui_device.format) + + if ui_action.isCreate and ui_action.isDevice: + # We're going to just move the already-defined device into the + # main devicetree, but first we need to replace its parents + # with the corresponding devices from the main devicetree + # instead of the ones from our local devicetree. + parents = [self.storage.devicetree.getDeviceByID(p.id) + for p in ui_device.parents] + + if hasattr(ui_device, "partedPartition"): + # This cleans up any references to parted.Disk instances + # that only exist in our local devicetree. + ui_device.partedPartition = None + + req_disks = [self.storage.devicetree.getDeviceByID(p.id) + for p in ui_device.req_disks] + ui_device.req_disks = req_disks + + # If we somehow ended up with a different number of parents + # go ahead and take a dump on the floor. + assert(len(parents) == len(ui_device.parents)) + ui_device.parents = parents + args = [ui_device] + + log.debug("duplicating action '%s'" % ui_action) + action = ui_action.__class__(*args) + self.storage.devicetree.registerAction(action) def apply(self): + ui_devicetree = self.__storage.devicetree + + log.debug("converting custom spoke changes into actions") + for action in ui_devicetree.findActions(): + log.debug("%s" % action) + + # schedule actions for device removals, resizes + actions = ui_devicetree.findActions(type="destroy") + partition_destroys = [a for a in actions + if a.device.type == "partition"] + actions.extend(ui_devicetree.findActions(type="resize")) + self._propagate_actions(actions) + + # register all disklabel create actions + actions = ui_devicetree.findActions(type="create", object="format") + disklabel_creates = [a for a in actions + if a.device.format.type == "disklabel"] + self._propagate_actions(disklabel_creates) + + # register partition create actions, including formatting + actions = ui_devicetree.findActions(type="create") + partition_creates = [a for a in actions if a.device.type == "partition"] + self._propagate_actions(partition_creates) + + if partition_creates or partition_destroys: + try: + doPartitioning(self.storage) + except Exception as e: + # TODO: error handling + raise + + # register all other create actions + already_handled = disklabel_creates + partition_creates + actions = [a for a in ui_devicetree.findActions(type="create") + if a not in already_handled] + self._propagate_actions(actions) + + # set up bootloader and check the configuration self.storage.setUpBootLoader() StorageChecker.run(self) @@ -147,7 +257,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._configButton = self.builder.get_object("configureButton") def initialize(self): - from pyanaconda.storage.devices import DiskDevice from pyanaconda.storage.formats.fs import FS NormalSpoke.initialize(self) @@ -198,40 +307,40 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): else: return None + @property def _clearpartDevices(self): - return [d for d in self.storage.devicetree.devices if d.name in self.data.clearpart.drives] - - def _unusedDevices(self): - from pyanaconda.storage.devices import DiskDevice - return [d for d in self.storage.unusedDevices if d.disks and not isinstance(d, DiskDevice)] + return [d for d in self._devices if d.name in self.data.clearpart.drives] - def _currentFreeSpace(self): - """Add up all the free space on selected disks and return it as a Size.""" - totalFree = Size(bytes=0) + @property + def unusedDevices(self): + if self._unused_devices is None: + self._unused_devices = [d for d in self.__storage.unusedDevices + if d.disks and not d.isDisk] - freeDisks = self.storage.getFreeSpace(disks=self._clearpartDevices()) - for tup in freeDisks.values(): - for chunk in tup: - totalFree += chunk + return self._unused_devices - return totalFree + def _setCurrentFreeSpace(self): + """Add up all the free space on selected disks and return it as a Size.""" + freeDisks = self.__storage.getFreeSpace(clearPartType=CLEARPART_TYPE_NONE) + self._free_space = sum([f[0] for f in freeDisks.values()]) def _currentTotalSpace(self): """Add up the sizes of all selected disks and return it as a Size.""" totalSpace = 0 - for disk in self._clearpartDevices(): + for disk in self._clearpartDevices: totalSpace += disk.size return Size(spec="%s MB" % totalSpace) def _updateSpaceDisplay(self): # Set up the free space/available space displays in the bottom left. + self._setCurrentFreeSpace() self._availableSpaceLabel = self.builder.get_object("availableSpaceLabel") self._totalSpaceLabel = self.builder.get_object("totalSpaceLabel") self._summaryButton = self.builder.get_object("summary_button") - self._availableSpaceLabel.set_text(str(self._currentFreeSpace())) + self._availableSpaceLabel.set_text(str(self._free_space)) self._totalSpaceLabel.set_text(str(self._currentTotalSpace())) summaryLabel = self._summaryButton.get_children()[0] @@ -245,7 +354,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): def refresh(self): NormalSpoke.refresh(self) + self.__storage = self.storage.copy() + self.__storage.devicetree._actions = [] # keep things simple + self._devices = self.__storage.devices self._do_refresh() + + # update our free space number based on Storage + self._setCurrentFreeSpace() + self._updateSpaceDisplay() def _do_refresh(self): @@ -261,9 +377,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # We can only have one page expanded at a time. did_expand = False - unused = self._unusedDevices() - new_devices = [d for d in self.storage.devicetree.leaves if d not in unused and not d.exists] - roots = self.storage.roots[:] + unused = [d for d in self.unusedDevices if d.isleaf and d in self._devices] + new_devices = [d for d in self._devices if not d.exists] + + log.debug("ui: unused=%s" % [d.name for d in unused]) + log.debug("ui: new_devices=%s" % [d.name for d in new_devices]) + + ui_roots = self.__storage.roots[:] # If we've not yet run autopart, add an instance of CreateNewPage. This # ensures it's only added once. @@ -278,39 +398,44 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): label = self.builder.get_object("whenCreateLabel") label.set_text(self._when_create_text % (productName, productVersion)) else: - swaps = [d for d in new_devices if d in self.storage.swaps] - mounts = dict([(d.format.mountpoint, d) for d in new_devices if getattr(d.format, "mountpoint", None)]) + swaps = [d for d in new_devices if d.format.type == "swap"] + mounts = dict([(d.format.mountpoint, d) for d in new_devices + if getattr(d.format, "mountpoint", None)]) new_root = Root(mounts=mounts, swaps=swaps, name=new_install_name) - roots.insert(0, new_root) + ui_roots.insert(0, new_root) # Add in all the existing (or autopart-created) operating systems. - for root in roots: - if root.device and root.device not in self.storage.devices: + for root in ui_roots: + # don't make a page if none of the root's devices are left + if not [d for d in root.swaps + root.mounts.values() + if d in self._devices]: continue page = Page() page.pageTitle = root.name for device in root.swaps: - if device not in self.storage.devices: + if device not in self._devices: continue - selector = page.addDevice("Swap", device.size, None, self.on_selector_clicked) + selector = page.addDevice("Swap", + Size(spec="%f MB" % device.size), + None, self.on_selector_clicked) selector._device = device selector._root = root for (mountpoint, device) in root.mounts.iteritems(): - if device not in self.storage.devices: + if device not in self._devices: continue - selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, device.size, mountpoint, self.on_selector_clicked) + selector = page.addDevice(self._mountpointName(mountpoint) or device.format.name, Size(spec="%f MB" % device.size), mountpoint, self.on_selector_clicked) selector._device = device selector._root = root page.show_all() self._accordion.addPage(page, cb=self.on_page_clicked) - if not did_expand and getattr(self._current_selector, "_root", None) and root.name == self._current_selector._root.name: + if not did_expand: did_expand = True self._accordion.expandPage(root.name) @@ -320,14 +445,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): page.pageTitle = _("Unknown") for u in unused: - selector = page.addDevice(u.format.name, u.size, None, self.on_selector_clicked) + selector = page.addDevice(u.format.name, Size(spec="%f MB" % u.size), None, self.on_selector_clicked) selector._device = u selector._root = None page.show_all() self._accordion.addPage(page, cb=self.on_page_clicked) - if not did_expand and self._current_selector and unused == self._current_selector._root: + if not did_expand: did_expand = True self._accordion.expandPage(page.pageTitle) @@ -361,7 +486,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): device = selector._device - if hasattr(device.format, "label") and labelEntry.get_text(): + if labelEntry.get_text() and hasattr(device.format, "label"): device.format.label = labelEntry.get_text() def _populate_right_side(self, selector): @@ -379,14 +504,16 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): selectedDeviceDescLabel.set_text(self._description(selector.props.name)) labelEntry.set_text(getattr(device.format, "label", "") or "") - labelEntry.set_sensitive(getattr(device.format, "labelfsProg", "") != "") + can_label = getattr(device.format, "labelfsProg", "") != "" + labelEntry.set_sensitive(can_label) if labelEntry.get_sensitive(): labelEntry.props.has_tooltip = False else: labelEntry.set_tooltip_text(_("This file system does not support labels.")) - sizeSpinner.set_range(device.minSize, device.maxSize) + sizeSpinner.set_range(device.minSize, + device.maxSize) sizeSpinner.set_value(device.size) sizeSpinner.set_sensitive(device.resizable) @@ -400,15 +527,17 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # FIXME: What do we do if we can't figure it out? if device.type == "lvmlv": typeCombo.set_active(1) - elif device.type in ["dm-raid array", "mdarray"]: + elif device.type == "mdarray": typeCombo.set_active(2) elif device.type == "partition": typeCombo.set_active(3) + elif device.type.startswith("btrfs"): + typeCombo.set_active(0) # FIXME: What do we do if we can't figure it out? model = fsCombo.get_model() for i in range(0, len(model)): - if model[i][0] == device.format.name: + if model[i][0] == device.format.type: fsCombo.set_active(i) break @@ -439,47 +568,68 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # create a device of the default type, using any disks, with an # appropriate fstype and mountpoint mountpoint = dialog.mountpoint - size = dialog.size - fstype = self.storage.defaultFSType + log.debug("requested size = %s ; available space = %s" + % (dialog.size, self._free_space)) + size = min(dialog.size, self._free_space) + fstype = self.storage.getFSType(mountpoint) + encrypted = self.data.autopart.encrypted + + # we're doing nothing here to ensure that bootable requests end up on + # the boot disk, but the weight from platform should take care of this + if mountpoint.lower() == "swap": - fstype = "swap" mountpoint = None - elif mountpoint == "/boot": - fstype = self.storage.defaultBootFSType - elif mountpoint == "/boot/efi": - if iutil.isMactel(): - fstype = "hfs+" - else: - fstype = "efi" - elif not mountpoint or not mountpoint.startswith("/") or \ - mountpoint in self.storage.mountpoints.keys(): - return - if not size: - # no size specified, so use the default size - size = None - - if self.data.autopart.type == AUTOPART_TYPE_PLAIN: - # create a partition for this new filesystem - size_mb = float(size.convertTo(spec="mb")) - device = self.storage.newPartition(size=size_mb, - fmt_type=fstype, - mountpoint=mountpoint) - self.storage.createDevice(device) - try: - doPartitioning(self.storage) - except StorageError as e: - actions = self.storage.devicetree.findActions(device=device) - for a in reversed(actions): - self.storage.devicetree.cancelAction(a) - else: - self._do_refresh() + # TODO: validate the mountpoint for sanity and uniqueness + + device_type = self.data.autopart.type + if device_type == AUTOPART_TYPE_LVM and \ + mountpoint == "/boot/efi": + device_type = AUTOPART_TYPE_PLAIN + elif device_type == AUTOPART_TYPE_BTRFS and \ + (fstype == "swap" or \ + (mountpoint and mountpoint.startswith("/boot"))): + device_type = AUTOPART_TYPE_PLAIN + + disks = self._clearpartDevices + + with ui_storage_logger(): + self.__storage.newDevice(device_type, + size=float(size.convertTo(spec="mb")), + fstype=fstype, + mountpoint=mountpoint, + encrypted=encrypted, + disks=disks) + self._devices = self.__storage.devices + self._do_refresh() + self._updateSpaceDisplay() def _destroy_device(self, device): + with ui_storage_logger(): + self.__storage.destroyDevice(device) + + self._devices = self.__storage.devices + + # should this be in DeviceTree._removeDevice? + container = None + if hasattr(device, "vg"): + device.vg._removeLogVol(device) + container = device.vg + device_type = AUTOPART_TYPE_LVM + elif hasattr(device, "volume"): + device.volume._removeSubVolume(device.name) + container = device.volume + device_type = AUTOPART_TYPE_BTRFS + + if container and not container.exists: + # adjust container to size of remaining devices + with ui_storage_logger(): + # TODO: raid + factory = self.__storage.getDeviceFactory(device_type, 0) + parents = self.__storage.setContainerMembers(container, factory) + # if this device has parents with no other children, remove them too - parents = device.parents[:] - self.storage.destroyDevice(device) - for parent in parents: + for parent in device.parents: if parent.kids == 0 and not parent.isDisk: self._destroy_device(parent) @@ -488,8 +638,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): pass # unused device elif device in root.swaps: root.swaps.remove(device) - elif hasattr(device.format, "mountpoint") and \ - device in root.mounts.values(): + elif device in root.mounts.values(): mountpoints = [m for (m,d) in root.mounts.items() if d == device] for mountpoint in mountpoints: root.mounts.pop(mountpoint) @@ -520,7 +669,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): # schedule actions to delete the thing. dialog = ConfirmDeleteDialog(self.data) with enlightbox(self.window, dialog.window): - dialog.refresh(getattr(device.format, "mountpoint", None), device.name) + dialog.refresh(getattr(device.format, "mountpoint", ""), + device.name) rc = dialog.run() if rc == 0: @@ -530,12 +680,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._remove_from_root(root, device) self._destroy_device(device) - - # If the root is now empty, remove it. Devices from the Unused page - # will have no root. - if root and root in self.storage.roots and len(root.swaps + root.mounts.values()) == 0: - self.storage.roots.remove(root) - self._update_ui_for_removals() elif self._accordion.currentPage(): # This is a complete installed system. Thus, we first need to confirm @@ -562,19 +706,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): for device in root.swaps + root.mounts.values(): self._destroy_device(device) - # Remove the root entirely. This will cause the empty Page to be - # deleted from the left hand side. - if root in self.storage.roots: - self.storage.roots.remove(root) - self._update_ui_for_removals() def on_summary_clicked(self, button): - free = self.storage.getFreeSpace() + free = self._free_space dialog = SelectedDisksDialog(self.data) with enlightbox(self.window, dialog.window): - dialog.refresh(self._clearpartDevices(), free, showRemove=False) + dialog.refresh(self._clearpartDevices, free, showRemove=False) dialog.run() def on_configure_clicked(self, button): @@ -608,20 +747,28 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._removeButton.set_sensitive(True) - def on_create_clicked(self, button): - from pyanaconda.storage import Root - from pyanaconda.storage.devices import DiskDevice - from pyanaconda.storage.partitioning import doAutoPartition + def _do_autopart(self): + # There are never any non-existent devices around when this runs. + log.debug("running automatic partitioning") + self.__storage.doAutoPart = True + with ui_storage_logger(): + doAutoPartition(self.__storage, self.data) + self.__storage.doAutoPart = False + self._devices = self.__storage.devices + log.debug("finished automatic partitioning") + def on_create_clicked(self, button): # Then do autopartitioning. We do not do any clearpart first. This is # custom partitioning, so you have to make your own room. - # FIXME: Handle all the autopart exns here. - self.data.autopart.autopart = True - self.data.autopart.execute(self.storage, self.data, self.instclass) - self.data.autopart.autopart = False + self._do_autopart() # Refresh the spoke to make the new partitions appear. - self.refresh() + log.debug("refreshing ui") + self._do_refresh() + log.debug("finished refreshing ui") + log.debug("updating space display") + self._updateSpaceDisplay() + log.debug("finished updating space display") self._accordion.expandPage(new_install_name) # And then display the first filesystem on the RHS. @@ -641,3 +788,5 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): self._optionsNotebook.set_current_page(2) elif text == _("Standard Partition"): self._optionsNotebook.hide() + + diff --git a/pyanaconda/ui/gui/spokes/lib/accordion.py b/pyanaconda/ui/gui/spokes/lib/accordion.py index c2da71ecb..593d4d545 100644 --- a/pyanaconda/ui/gui/spokes/lib/accordion.py +++ b/pyanaconda/ui/gui/spokes/lib/accordion.py @@ -23,7 +23,6 @@ import gettext _ = lambda x: gettext.ldgettext("anaconda", x) from pyanaconda.product import productName, productVersion -from pyanaconda.storage.size import Size from gi.repository.AnacondaWidgets import MountpointSelector from gi.repository import Gtk @@ -75,7 +74,7 @@ class Accordion(Gtk.Box): def expandPage(self, pageTitle): page = self._find_by_title(pageTitle) - if page: + if page and not page.get_expanded(): page.emit("activate") def removePage(self, pageTitle): @@ -137,7 +136,7 @@ class Page(Gtk.Box): def addDevice(self, name, size, mountpoint, cb): selector = MountpointSelector() - selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "") + selector = MountpointSelector(name, str(size).upper(), mountpoint or "") selector.connect("button-press-event", self._onSelectorClicked, cb) selector.connect("key-release-event", self._onSelectorClicked, cb) selector.connect("focus-in-event", self._onSelectorClicked, cb) @@ -182,7 +181,7 @@ class UnknownPage(Page): def addDevice(self, name, size, mountpoint, cb): selector = MountpointSelector() - selector = MountpointSelector(name, str(Size(spec="%s MB" % size)), mountpoint or "") + selector = MountpointSelector(name, str(size).upper(), mountpoint or "") selector.connect("button-press-event", self._onSelectorClicked, cb) selector.connect("key-release-event", self._onSelectorClicked, cb) selector.connect("focus-in-event", self._onSelectorClicked, cb) diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py index d8cccd8c0..b560f5568 100644 --- a/pyanaconda/ui/gui/spokes/storage.py +++ b/pyanaconda/ui/gui/spokes/storage.py @@ -312,6 +312,7 @@ class StorageSpoke(NormalSpoke, StorageChecker): self.data.clearpart.initAll = True self.data.clearpart.type = self.clearPartType self.storage.config.update(self.data) + self.storage.autoPartType = self.data.autopart.type # If autopart is selected we want to remove whatever has been # created/scheduled to make room for autopart. @@ -515,8 +516,23 @@ class StorageSpoke(NormalSpoke, StorageChecker): clearPartType=CLEARPART_TYPE_ALL) disk_free = sum([f[0] for f in free_space.itervalues()]) fs_free = sum([f[1] for f in free_space.itervalues()]) + + # add in total size of new filesystems + new_free_mb = 0 + for mountpoint, device in self.storage.mountpoints.items(): + if mountpoint != "/" and not mountpoint.startswith("/usr") and \ + not mountpoint.startswith("/var"): + # only count system mounts + continue + + if device.format.exists: + new_free_mb += getattr(device.format, "free", 0) + else: + new_free_mb += device.size + new_free = Size(spec="%f MB" % new_free_mb) + required_space = self.payload.spaceRequired - if disk_free >= required_space: + if disk_free + new_free >= required_space: dialog = InstallOptions1Dialog(self.data) elif sum([d.size for d in disks]) >= required_space: dialog = InstallOptions2Dialog(self.data) |