diff options
author | Martin Sivak <msivak@redhat.com> | 2012-08-06 13:43:32 +0200 |
---|---|---|
committer | Martin Sivak <msivak@redhat.com> | 2012-08-06 13:43:32 +0200 |
commit | a518f8919488710842dc50d6e28f1d9bea5a186d (patch) | |
tree | ef1d8e64aef6cf133d4f92177eea3ed6644b49f1 /pyanaconda | |
parent | 72c797294e5d70a221cab91d3d97ce805ad4b851 (diff) | |
parent | 86dd8ff76ba9a6fd10a7325df2c0691339d7fb76 (diff) | |
download | anaconda-a518f8919488710842dc50d6e28f1d9bea5a186d.tar.gz anaconda-a518f8919488710842dc50d6e28f1d9bea5a186d.tar.xz anaconda-a518f8919488710842dc50d6e28f1d9bea5a186d.zip |
Merge master into newtui
Diffstat (limited to 'pyanaconda')
72 files changed, 1308 insertions, 1447 deletions
diff --git a/pyanaconda/__init__.py b/pyanaconda/__init__.py index eb44ca90e..cf40ccd04 100644 --- a/pyanaconda/__init__.py +++ b/pyanaconda/__init__.py @@ -261,16 +261,18 @@ class Anaconda(object): self.methodstr = methodstr def write(self): + import network self.writeXdriver() self.instLanguage.write() self.timezone.write() + network.write_sysconfig_network() + network.disableIPV6() + network.copyConfigToPath(ROOT_PATH) if not self.ksdata: - self.instClass.setNetworkOnbootDefault(self.network) - self.network.write() - self.network.copyConfigToPath() - self.network.disableNMForStorageDevices(self) - self.network.autostartFCoEDevices(self) + self.instClass.setNetworkOnbootDefault() + network.disableNMForStorageDevices(self.storage) + network.autostartFCoEDevices(self.storage) self.desktop.write() self.security.write() self.firewall.write() diff --git a/pyanaconda/anaconda_log.py b/pyanaconda/anaconda_log.py index f9d884e73..40c1938f2 100644 --- a/pyanaconda/anaconda_log.py +++ b/pyanaconda/anaconda_log.py @@ -44,6 +44,7 @@ MAIN_LOG_FILE = "/tmp/anaconda.log" MAIN_LOG_TTY = "/dev/tty3" PROGRAM_LOG_FILE = "/tmp/program.log" STORAGE_LOG_FILE = "/tmp/storage.log" +PACKAGING_LOG_FILE = "/tmp/packaging.log" ANACONDA_SYSLOG_FACILITY = SysLogHandler.LOG_LOCAL1 logLevelMap = {"debug": logging.DEBUG, "info": logging.INFO, @@ -151,6 +152,13 @@ class AnacondaLog: minLevel=logging.DEBUG) self.forwardToSyslog(program_logger) + # Create the packaging logger. + packaging_logger = logging.getLogger("packaging") + packaging_logger.setLevel(logging.DEBUG) + self.addFileHandler(PACKAGING_LOG_FILE, packaging_logger, + minLevel=logging.DEBUG) + self.forwardToSyslog(packaging_logger) + # Create a second logger for just the stuff we want to dup on # stdout. Anything written here will also get passed up to the # parent loggers for processing and possibly be written to the @@ -224,7 +232,7 @@ class AnacondaLog: def setupVirtio(self): """Setup virtio rsyslog logging. """ - TEMPLATE = "*.* %s\n" + TEMPLATE = "*.* %s;anaconda_syslog\n" if not os.path.exists(self.VIRTIO_PORT) \ or not os.access(self.VIRTIO_PORT, os.W_OK): diff --git a/pyanaconda/backend.py b/pyanaconda/backend.py index 53e8a9f05..4a3e6c1c5 100644 --- a/pyanaconda/backend.py +++ b/pyanaconda/backend.py @@ -25,7 +25,6 @@ import shutil import iutil import os, sys import logging -import backend_log from constants import * import isys @@ -98,7 +97,6 @@ class AnacondaBackend: storage.writeEscrowPackets(anaconda) sys.stdout.flush() - backend_log.log.stop() def doInstall(self, anaconda): log.warning("doInstall not implemented for backend!") @@ -126,7 +124,6 @@ class AnacondaBackend: shutil.rmtree (syslogname) except OSError: pass - backend_log.log.start(instPath, syslogname) if self.anaconda.upgrade: self.modeText = _("%s Upgrading %s\n") diff --git a/pyanaconda/backend_log.py b/pyanaconda/backend_log.py deleted file mode 100644 index 9125d7303..000000000 --- a/pyanaconda/backend_log.py +++ /dev/null @@ -1,95 +0,0 @@ -# backend_log.py -# Logging infrastructure for Anaconda's backend. -# -# Copyright (C) 2009 Red Hat, Inc. -# -# This copyrighted material is made available to anyone wishing to use, -# modify, copy, or redistribute it subject to the terms and conditions of -# the GNU General Public License v.2, or (at your option) any later version. -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY expressed or implied, including the implied warranties of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. You should have received a copy of the -# GNU General Public License along with this program; if not, write to the -# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the -# source code or documentation are not subject to the GNU General Public -# License and may only be used or replicated with the express permission of -# Red Hat, Inc. -# -# Red Hat Author(s): Ales Kozumplik <akozumpl@redhat.com> -# - -import logging -import os -import signal - -import anaconda_log -import iutil - -SYSLOG_PATH = '/sbin/rsyslogd' -SYSLOG_PIDFILE = '/var/run/rsyslog_backend.pid' -SYSLOG_CFGFILE = '/tmp/rsyslog_backend.conf' - -CFG_TEMPLATE = """ -$ModLoad imuxsock -$InputUnixListenSocketHostName sysimage -$AddUnixListenSocket %(socket)s -+sysimage -*.* %(logfile)s;RSYSLOG_TraditionalFileFormat -%(remote_syslog)s -""" - -global_log = logging.getLogger("anaconda") -class BackendSyslog: - def __init__(self): - pass - - def build_cfg(self, root, log): - socket = "%s/dev/log" % (root, ) - remote_syslog = '' - if anaconda_log.logger.remote_syslog: - remote_syslog = "*.* @@%s" % (anaconda_log.logger.remote_syslog, ) - - cfg = CFG_TEMPLATE % { - 'socket' : socket, - 'logfile' : log, - 'remote_syslog' : remote_syslog - } - with open(SYSLOG_CFGFILE, 'w') as cfg_file: - cfg_file.write(cfg) - - def start(self, root, log): - """ Start an rsyslogd instance dedicated for the sysimage. - - Other possibility would be to change configuration and SIGHUP the - existing instance, but it could lose some of its internal queues and - give us problems with remote logging. - """ - self.build_cfg(root, log) - args = ['-c', '4', - '-f', SYSLOG_CFGFILE, - '-i', str(SYSLOG_PIDFILE)] - status = iutil.execWithRedirect(SYSLOG_PATH, args) - if status == 0: - global_log.info("Backend logger started.") - else: - global_log.error("Unable to start backend logger") - - def stop(self): - try: - with open(SYSLOG_PIDFILE, 'r') as pidfile: - pid = int(pidfile.read()) - os.kill(pid, signal.SIGKILL) - except Exception: - pass - else: - global_log.info("Backend logger stopped.") - - try: - os.unlink(SYSLOG_CFGFILE) - except OSError as e: - global_log.error("Failed to unlink backend logger config file: %s" - % e) - -log = BackendSyslog() diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index 991c8de80..ed8040ff5 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -28,12 +28,13 @@ import struct from pyanaconda import iutil from pyanaconda.storage.devicelibs import mdraid -from pyanaconda.isys import sync +from pyanaconda.isys import sync, getMacAddress from pyanaconda.product import productName from pyanaconda.flags import flags from pyanaconda.constants import * from pyanaconda.storage.errors import StorageError from pyanaconda.storage.fcoe import fcoe +import pyanaconda.network import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -750,14 +751,11 @@ class BootLoader(object): Keyword Arguments: storage - a pyanaconda.storage.Storage instance - network - a pyanaconda.network.Network instance (for network - storage devices' boot arguments) All other arguments are expected to have a dracutSetupArgs() method. """ storage = kwargs.pop("storage", None) - network = kwargs.pop("network", None) # # FIPS @@ -811,15 +809,7 @@ class BootLoader(object): # network storage # XXX this is nothing to be proud of if isinstance(dep, NetworkStorageDevice): - if network is None: - log.error("missing network instance for setup of boot " - "command line for network storage device %s" - % dep.name) - raise BootLoaderError("missing network instance when " - "setting boot args for network " - "storage device") - - setup_args = network.dracutSetupArgs(dep) + setup_args = pyanaconda.network.dracutSetupArgs(dep) self.boot_args.update(setup_args) self.dracut_args.update(setup_args) @@ -845,7 +835,7 @@ class BootLoader(object): # Dracut needs the explicit ifname= because biosdevname # fails to rename the iface (because of BFS booting from it). for nic, dcb, auto_vlan in fcoe().nics: - hwaddr = network.netdevices[nic].get("HWADDR") + hwaddr = getMacAddress(nic) self.boot_args.add("ifname=%s:%s" % (nic, hwaddr.lower())) # @@ -1485,6 +1475,10 @@ class GRUB2(GRUB): def write_config(self): self.write_config_console(None) + # See if we have a password and if so update the boot args before we + # write out the defaults file. + if self.password or self.encrypted_password: + self.boot_args.add("rd.shell=0") self.write_defaults() # if we fail to setup password auth we should complete the @@ -1522,13 +1516,13 @@ class GRUB2(GRUB): # XXX will installing to multiple drives work as expected with GRUBv2? for (stage1dev, stage2dev) in self.install_targets: - args += ["--no-floppy", stage1dev.path] + grub_args = args + ["--no-floppy", stage1dev.path] if stage1dev == stage2dev: # This is hopefully a temporary hack. GRUB2 currently refuses # to install to a partition's boot block without --force. - args.insert(0, '--force') + grub_args.insert(0, '--force') - rc = iutil.execWithRedirect("grub2-install", args, + rc = iutil.execWithRedirect("grub2-install", grub_args, stdout="/dev/tty5", stderr="/dev/tty5", root=ROOT_PATH, env_prune=['MALLOC_PERTURB_']) @@ -1668,6 +1662,8 @@ class YabootSILOBase(BootLoader): continue args = Arguments() + if self.password or self.encrypted_password: + args.add("rd.shell=0") if image.initrd: initrd_line = "\tinitrd=%s/%s\n" % (self.boot_prefix, image.initrd) @@ -1996,7 +1992,21 @@ class ZIPL(BootLoader): # DWL FIXME: resolve the boot device to a StorageDevice from storage buf = iutil.execWithCapture("zipl", [], stderr="/dev/tty5", - root=ROOT_PATH) + root=ROOT_PATH, + fatal=True) + for line in buf.splitlines(): + if line.startswith("Preparing boot device: "): + # Output here may look like: + # Preparing boot device: dasdb (0200). + # Preparing boot device: dasdl. + # We want to extract the device name and pass that. + name = re.sub(".+?: ", "", line) + name = re.sub("(\s\(.+\))?\.$", "", name) + device = self.storage.devicetree.getDeviceByName(name) + if not device: + raise BootLoaderError("could not find IPL device") + + self.stage1_device = device class SILO(YabootSILOBase): name = "SILO" diff --git a/pyanaconda/errors.py b/pyanaconda/errors.py index 5d823de23..37ee4d01a 100644 --- a/pyanaconda/errors.py +++ b/pyanaconda/errors.py @@ -241,7 +241,7 @@ class ErrorHandler(object): rc = ERROR_RAISE if not self.ui: - raise exn + raise _map = {KickstartError: self._kickstartErrorHandler, StorageError.PartitioningError: self._partitionErrorHandler, diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py index 47ccd1e4b..70deed5d4 100644 --- a/pyanaconda/exception.py +++ b/pyanaconda/exception.py @@ -31,22 +31,49 @@ from flags import flags import kickstart import storage.errors from pyanaconda.constants import ROOT_PATH +from gi.repository import GLib import logging log = logging.getLogger("anaconda") +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + + class AnacondaExceptionHandler(ExceptionHandler): def handleException(self, (ty, value, tb), obj): - import traceback - # Save the exception to the filesystem first. - self.exn = self.exnClass((ty, value, tb), self.conf) - (fd, self.exnFile) = self.openFile() - text = self.exn.write(obj, fd) - fd.close() - - traceback.print_exception(ty, value, tb) - os._exit(10) + def run_handleException_on_idle(args_tuple): + """ + Helper function with one argument only so that it can be registered + with GLib.idle_add() to run on idle. + + @param args_tuple: ((ty, value, tb), obj) + + """ + + trace, obj = args_tuple + ty, value, tb = trace + + super(AnacondaExceptionHandler, self).handleException((ty, value, tb), + obj) + + if issubclass(ty, storage.errors.StorageError) and value.hardware_fault: + hw_error_msg = _("The installation was stopped due to what " + "seems to be a problem with your hardware. " + "The exact error message is:\n\n%s.\n\n " + "The installer will now terminate.") % str(value) + self.intf.showError(hw_error_msg) + sys.exit(0) + else: + if GLib.main_depth() > 0: + # main loop is running, don't crash it by running another one + # potentially from a different thread + GLib.idle_add(run_handleException_on_idle, + ((ty, value, tb), obj)) + else: + super(AnacondaExceptionHandler, self).handleException( + (ty, value, tb), obj) def postWriteHook(self, (ty, value, tb), anaconda): # See if /mnt/sysimage is present and put exception there as well @@ -72,8 +99,6 @@ class AnacondaExceptionHandler(ExceptionHandler): except SystemError: pass - self.intf.__del__ () - pidfl = "/tmp/vncshell.pid" if os.path.exists(pidfl) and os.path.isfile(pidfl): pf = open(pidfl, "r") @@ -97,9 +122,14 @@ class AnacondaExceptionHandler(ExceptionHandler): termios.tcsetattr(si, termios.TCSADRAIN, attr) print("\nEntering debugger...") + print("Use 'continue' command to quit the debugger and get back to "\ + "the main window") import pdb pdb.post_mortem (tb) - os.kill(os.getpid(), signal.SIGKILL) + try: + isys.vtActivate(6) + except SystemError: + pass def initExceptionHandling(anaconda): fileList = [ "/tmp/anaconda.log", diff --git a/pyanaconda/install.py b/pyanaconda/install.py index be31e75b5..3ea3bd5ee 100644 --- a/pyanaconda/install.py +++ b/pyanaconda/install.py @@ -62,7 +62,7 @@ def doInstall(storage, payload, ksdata, instClass): turnOnFilesystems(storage) # Do packaging. - payload.preInstall(packages=storage.packages) + payload.preInstall(packages=storage.packages, groups=payload.languageGroups(ksdata.lang.lang)) payload.install() with progress_report(_("Performing post-install setup tasks")): @@ -74,6 +74,7 @@ def doInstall(storage, payload, ksdata, instClass): # Now run the execute methods of ksdata that require an installed system # to be present first. + ksdata.authconfig.execute(storage, ksdata, instClass) ksdata.firstboot.execute(storage, ksdata, instClass) ksdata.services.execute(storage, ksdata, instClass) ksdata.keyboard.execute(storage, ksdata, instClass) diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py index 3f291059a..cdef54215 100644 --- a/pyanaconda/installclass.py +++ b/pyanaconda/installclass.py @@ -32,6 +32,7 @@ import types from constants import * from product import * from storage.partspec import * +from storage.devicelibs import swap import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -116,9 +117,9 @@ class BaseInstallClass(object): if bootreq: autorequests.extend(bootreq) - (minswap, maxswap) = iutil.swapSuggestion() - autorequests.append(PartSpec(fstype="swap", size=minswap, maxSize=maxswap, - grow=True, lv=True, encrypted=True)) + swp = swap.swapSuggestion() + autorequests.append(PartSpec(fstype="swap", size=swp, grow=False, + lv=True, encrypted=True)) storage.autoPartitionRequests = autorequests @@ -153,7 +154,7 @@ class BaseInstallClass(object): return (all(result.values()), result) - def setNetworkOnbootDefault(self, network): + def setNetworkOnbootDefault(self): pass def __init__(self): diff --git a/pyanaconda/installclasses/fedora.py b/pyanaconda/installclasses/fedora.py index bc575ea8d..e2c15433a 100644 --- a/pyanaconda/installclasses/fedora.py +++ b/pyanaconda/installclasses/fedora.py @@ -21,7 +21,7 @@ from pyanaconda.installclass import BaseInstallClass from pyanaconda.constants import * from pyanaconda.product import * from pyanaconda import iutil -from pyanaconda.network import hasActiveNetDev +from pyanaconda import network from pyanaconda import isys import os, types @@ -122,17 +122,20 @@ class InstallClass(BaseInstallClass): # than two versions ago! return newVer >= oldVer and newVer - oldVer <= 2 - def setNetworkOnbootDefault(self, network): + def setNetworkOnbootDefault(self): # if something's already enabled, we can just leave the config alone - for devName, dev in network.netdevices.items(): - if dev.get('ONBOOT') == 'yes': + for devName in network.getDevices(): + if network.get_ifcfg_value(devName, "ONBOOT", ROOT_PATH) == "yes": return # the default otherwise: bring up the first wired netdev with link - for devName, dev in network.netdevices.items(): + for devName in network.getDevices(): if (not isys.isWirelessDevice(devName) and isys.getLinkStatus(devName)): + dev = network.NetworkDevice(ROOT_PATH + network.netscriptsDir, devName) + dev.loadIfcfgFile() dev.set(('ONBOOT', 'yes')) + dev.writeIfcfgFile() break def __init__(self): diff --git a/pyanaconda/installclasses/rhel.py b/pyanaconda/installclasses/rhel.py index 339b5b833..25edbae41 100644 --- a/pyanaconda/installclasses/rhel.py +++ b/pyanaconda/installclasses/rhel.py @@ -96,6 +96,9 @@ class InstallClass(BaseInstallClass): return False def versionMatches(self, oldver): + if oldver is None: + return False + oldMajor = oldver.split(".")[0] newMajor = productVersion.split(".")[0] diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py index 122894695..00fcb6ad7 100644 --- a/pyanaconda/iutil.py +++ b/pyanaconda/iutil.py @@ -200,8 +200,10 @@ def execWithRedirect(command, argv, stdin = None, stdout = None, # @param stdin The file descriptor to read stdin from. # @param stderr The file descriptor to redirect stderr to. # @param root The directory to chroot to before running command. +# @param fatal Boolean to determine if non-zero exit is fatal. # @return The output of command from stdout. -def execWithCapture(command, argv, stdin = None, stderr = None, root='/'): +def execWithCapture(command, argv, stdin = None, stderr = None, root='/', + fatal = False): if flags.testing: log.info("not running command because we're testing: %s %s" % (command, " ".join(argv))) @@ -257,6 +259,10 @@ def execWithCapture(command, argv, stdin = None, stderr = None, root='/'): if proc.returncode is not None: break + # if we have anything other than a clean exit, and we get the fatal + # option, raise the OSError. + if proc.returncode and fatal: + raise OSError('Non-zero return code: %s' % proc.returncode) except OSError as e: log.error ("Error running " + command + ": " + e.strerror) closefds() @@ -459,34 +465,6 @@ def memInstalled(): return long(mem) -## Suggest the size of the swap partition that will be created. -# @param quiet Should size information be logged? -# @return A tuple of the minimum and maximum swap size, in megabytes. -def swapSuggestion(quiet=0): - mem = memInstalled()/1024 - mem = ((mem/16)+1)*16 - if not quiet: - log.info("Detected %sM of memory", mem) - - if mem <= 256: - minswap = 256 - maxswap = 512 - else: - if mem > 2048: - minswap = 1024 - maxswap = 2048 + mem - else: - minswap = mem - maxswap = 2*mem - - if isS390(): - minswap = 1 - - if not quiet: - log.info("Swap attempt of %sM to %sM", minswap, maxswap) - - return (minswap, maxswap) - ## Create a directory path. Don't fail if the directory already exists. # @param dir The directory path to create. def mkdirChain(dir): @@ -501,19 +479,6 @@ def mkdirChain(dir): log.error("could not create directory %s: %s" % (dir, e.strerror)) -## Get the total amount of swap memory. -# @return The total amount of swap memory in kilobytes, or 0 if unknown. -def swapAmount(): - f = open("/proc/meminfo", "r") - lines = f.readlines() - f.close() - - for l in lines: - if l.startswith("SwapTotal:"): - fields = string.split(l) - return int(fields[1]) - return 0 - ## Copy a device node. # Copies a device node by looking at the device type, major and minor device # numbers, and doing a mknod on the new device name. @@ -1082,6 +1047,16 @@ def dracut_eject(device): except Exception, e: log.error("Error writing dracut shutdown eject hook for %s: %s" % (device, e)) +def get_option_value(opt_name, options): + """ Return the value of a named option in the specified options string. """ + for opt in options.split(","): + if "=" not in opt: + continue + + name, val = opt.split("=") + if name == opt_name: + return val.strip() + class ProxyStringError(Exception): pass diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index 585911937..7c3baa2c1 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -23,7 +23,9 @@ from storage.deviceaction import * from storage.devices import LUKSDevice from storage.devicelibs.lvm import getPossiblePhysicalExtents from storage.devicelibs.mpath import MultipathConfigWriter, MultipathTopology +from storage.devicelibs import swap from storage.formats import getFormat +from storage.partitioning import doPartitioning import storage.iscsi import storage.fcoe import storage.zfcp @@ -37,6 +39,7 @@ import os.path import tempfile from flags import flags from constants import * +import shlex import sys import urlgrabber import network @@ -49,7 +52,9 @@ from pyanaconda import ntp from pykickstart.base import KickstartCommand from pykickstart.constants import * from pykickstart.errors import formatErrorMsg, KickstartError, KickstartValueError -from pykickstart.parser import Group, KickstartParser, Script +from pykickstart.parser import KickstartParser +from pykickstart.parser import Group as PackageGroup +from pykickstart.parser import Script as KSScript from pykickstart.sections import * from pykickstart.version import returnClassForVersion @@ -70,7 +75,7 @@ packagesSeen = False # so it needs to know about them in some additional way: have the topology ready. topology = None -class AnacondaKSScript(Script): +class AnacondaKSScript(KSScript): def run(self, chroot, serial): if self.inChroot: scriptRoot = chroot @@ -212,6 +217,22 @@ def removeExistingFormat(device, storage): ### SUBCLASSES OF PYKICKSTART COMMAND HANDLERS ### +class Authconfig(commands.authconfig.FC3_Authconfig): + def execute(self, *args): + args = ["--update", "--nostart"] + shlex.split(self.authconfig) + + if not flags.automatedInstall and \ + (os.path.exists(ROOT_PATH + "/lib64/security/pam_fprintd.so") or \ + os.path.exists(ROOT_PATH + "/lib/security/pam_fprintd.so")): + args += ["--enablefingerprint"] + + try: + iutil.execWithRedirect("/usr/sbin/authconfig", args, + stdout="/dev/tty5", stderr="/dev/tty5", + root=ROOT_PATH) + except RuntimeError as msg: + log.error("Error running /usr/sbin/authconfig %s: %s", args, msg) + class AutoPart(commands.autopart.F17_AutoPart): def execute(self, storage, ksdata, instClass): from pyanaconda.platform import getPlatform @@ -289,6 +310,11 @@ class Bootloader(commands.bootloader.F18_Bootloader): if self.leavebootorder: flags.leavebootorder = True +class BTRFS(commands.btrfs.F17_BTRFS): + def execute(self, storage, ksdata, instClass): + for b in self.btrfsList: + b.execute(storage, ksdata, instClass) + class BTRFSData(commands.btrfs.F17_BTRFSData): def execute(self, storage, ksdata, instClass): devicetree = storage.devicetree @@ -416,7 +442,7 @@ class Fcoe(commands.fcoe.F13_Fcoe): class Firstboot(commands.firstboot.FC3_Firstboot): def execute(self, *args): - if not os.path.exists("/lib/systemd/system/firstboot-graphical.service"): + if not os.path.exists(ROOT_PATH + "/lib/systemd/system/firstboot-graphical.service"): return action = "enable" @@ -507,6 +533,11 @@ class IscsiName(commands.iscsiname.FC6_IscsiName): storage.iscsi.iscsi().initiator = self.iscsiname return retval +class LogVol(commands.logvol.F17_LogVol): + def execute(self, storage, ksdata, instClass): + for l in self.lvList: + l.execute(storage, ksdata, instClass) + class LogVolData(commands.logvol.F17_LogVolData): def execute(self, storage, ksdata, instClass): devicetree = storage.devicetree @@ -516,9 +547,9 @@ class LogVolData(commands.logvol.F17_LogVolData): if self.mountpoint == "swap": type = "swap" self.mountpoint = "" - if self.recommended: - (self.size, self.maxSizeMB) = iutil.swapSuggestion() - self.grow = True + if self.recommended or self.hibernation: + self.size = swap.swapSuggestion(hibernation=self.hibernation) + self.grow = False else: if self.fstype != "": type = self.fstype @@ -621,7 +652,7 @@ class LogVolData(commands.logvol.F17_LogVolData): request = storage.newLV(format=format, name=self.name, - vg=vg, + parents=[vg], size=self.size, grow=self.grow, maxsize=self.maxSizeMB, @@ -672,7 +703,7 @@ class NetworkData(commands.network.F16_NetworkData): def execute(self): if flags.imageInstall: if self.hostname != "": - self.anaconda.network.setHostname(self.hostname) + network.setHostname(self.hostname) # Only set hostname return @@ -681,22 +712,22 @@ class NetworkData(commands.network.F16_NetworkData): # only set hostname if self.essid: if self.hostname != "": - self.anaconda.network.setHostname(self.hostname) + network.setHostname(self.hostname) return - devices = self.anaconda.network.netdevices + devices = network.getDevices() if not self.device: - if self.anaconda.network.ksdevice: + if "ksdevice" in flags.cmdline: msg = "ksdevice boot parameter" - device = self.anaconda.network.ksdevice + device = network.get_ksdevice_name(flags.cmdline["ksdevice"]) elif network.hasActiveNetDev(): # device activated in stage 1 by network kickstart command msg = "first active device" device = network.getActiveNetDevs()[0] else: msg = "first device found" - device = min(devices.keys()) + device = min(devices) log.info("unspecified network --device in kickstart, using %s (%s)" % (device, msg)) else: @@ -722,18 +753,22 @@ class NetworkData(commands.network.F16_NetworkData): # If we were given a network device name, grab the device object. # If we were given a MAC address, resolve that to a device name # and then grab the device object. Otherwise, errors. - dev = None - if devices.has_key(device): - dev = devices[device] - else: - for (key, val) in devices.iteritems(): - if val.get("HWADDR").lower() == device.lower(): - dev = val + if device not in devices: + for d in devices: + if isys.getMacAddress(d).lower() == device.lower(): + device = d break + dev = network.NetworkDevice(ROOT_PATH, device) + try: + dev.loadIfcfgFile() + except IOError as e: + log.info("Can't load ifcfg file %s" % dev.path) + dev = None + if self.hostname != "": - self.anaconda.network.setHostname(self.hostname) + network.setHostname(self.hostname) if not dev: # Only set hostname return @@ -784,14 +819,18 @@ class NetworkData(commands.network.F16_NetworkData): dev.set(("ETHTOOL_OPTS", self.ethtool)) if self.nameserver != "": - self.anaconda.network.setDNS(self.nameserver, dev.iface) + dev.setDNS(self.nameserver) if self.gateway != "": - self.anaconda.network.setGateway(self.gateway, dev.iface) + dev.setGateway(self.gateway) if self.nodefroute: dev.set (("DEFROUTE", "no")) + #TODO + # write ifcfg file, carefuly handle ONBOOT value, + # problems - might activate the device!!! + class MultiPath(commands.multipath.FC6_MultiPath): def parse(self, args): raise NotImplementedError("The multipath kickstart command is not currently supported") @@ -800,6 +839,11 @@ class DmRaid(commands.dmraid.FC6_DmRaid): def parse(self, args): raise NotImplementedError("The dmraid kickstart command is not currently supported") +class Partition(commands.partition.F17_Partition): + def execute(self, storage, ksdata, instClass): + for p in self.partitions: + p.execute(storage, ksdata, instClass) + class PartitionData(commands.partition.F17_PartData): def execute(self, storage, ksdata, instClass): devicetree = storage.devicetree @@ -813,15 +857,15 @@ class PartitionData(commands.partition.F17_PartData): self.disk = disk break - if self.disk == "": + if not self.disk: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified BIOS disk %s cannot be determined" % self.onbiosdisk) if self.mountpoint == "swap": type = "swap" self.mountpoint = "" - if self.recommended: - (self.size, self.maxSizeMB) = iutil.swapSuggestion() - self.grow = True + if self.recommended or self.hibernation: + self.size = swap.swapSuggestion(hibernation=self.hibernation) + self.grow = False # if people want to specify no mountpoint for some reason, let them # this is really needed for pSeries boot partitions :( elif self.mountpoint == "None": @@ -947,12 +991,12 @@ class PartitionData(commands.partition.F17_PartData): should_clear = storage.shouldClear(disk) if disk and (disk.partitioned or should_clear): - kwargs["disks"] = [disk] + kwargs["parents"] = [disk] break elif disk: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified unpartitioned disk %s in partition command" % self.disk) - if not kwargs["disks"]: + if not kwargs["parents"]: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in partition command" % self.disk) kwargs["grow"] = self.grow @@ -1021,6 +1065,11 @@ class PartitionData(commands.partition.F17_PartData): parents=request) storage.createDevice(luksdev) +class Raid(commands.raid.F15_Raid): + def execute(self, storage, ksdata, instClass): + for r in self.raidList: + r.execute(storage, ksdata, instClass) + class RaidData(commands.raid.F15_RaidData): def execute(self, storage, ksdata, instClass): raidmems = [] @@ -1207,6 +1256,11 @@ class User(commands.user.F12_User): if not users.createUser(usr.name, **kwargs): log.error("User %s already exists, not creating." % usr.name) +class VolGroup(commands.volgroup.FC16_VolGroup): + def execute(self, storage, ksdata, instClass): + for v in self.vgList: + v.execute(storage, ksdata, instClass) + class VolGroupData(commands.volgroup.FC16_VolGroupData): def execute(self, storage, ksdata, instClass): pvs = [] @@ -1247,7 +1301,7 @@ class VolGroupData(commands.volgroup.FC16_VolGroupData): elif self.vgname in [vg.name for vg in storage.vgs]: raise KickstartValueError(formatErrorMsg(self.lineno, msg="The volume group name \"%s\" is already in use." % self.vgname)) else: - request = storage.newVG(pvs=pvs, + request = storage.newVG(parents=pvs, name=self.vgname, peSize=self.pesize/1024.0) @@ -1287,7 +1341,10 @@ class Keyboard(commands.keyboard.F18_Keyboard): # This is just the latest entry from pykickstart.handlers.control with all the # classes we're overriding in place of the defaults. commandMap = { + "auth": Authconfig, + "authconfig": Authconfig, "autopart": AutoPart, + "btrfs": BTRFS, "bootloader": Bootloader, "clearpart": ClearPart, "dmraid": DmRaid, @@ -1299,11 +1356,16 @@ commandMap = { "iscsiname": IscsiName, "keyboard": Keyboard, "logging": Logging, + "logvol": LogVol, "multipath": MultiPath, + "part": Partition, + "partition": Partition, + "raid": Raid, "rootpw": RootPw, "services": Services, "timezone": Timezone, "user": User, + "volgroup": VolGroup, "xconfig": XConfig, "zfcp": ZFCP, } @@ -1468,15 +1530,15 @@ def selectPackages(ksdata, payload): if errorHandler.cb(e) == ERROR_RAISE: sys.exit(1) - ksdata.packages.groupList.insert(0, Group("Core")) + ksdata.packages.groupList.insert(0, PackageGroup("Core")) if ksdata.packages.addBase: # Only add @base if it's not already in the group list. If the # %packages section contains something like "@base --optional", # addBase will take effect first and yum will think the group is # already selected. - if not Group("Base") in ksdata.packages.groupList: - ksdata.packages.groupList.insert(1, Group("Base")) + if not PackageGroup("Base") in ksdata.packages.groupList: + ksdata.packages.groupList.insert(1, PackageGroup("Base")) else: log.warning("not adding Base group") @@ -1506,3 +1568,17 @@ def selectPackages(ksdata, payload): payload.deselectGroup(grp.name) except NoSuchGroup: continue + +def doKickstartStorage(storage, ksdata, instClass): + """ Setup storage state from the kickstart data """ + ksdata.clearpart.execute(storage, ksdata, instClass) + ksdata.bootloader.execute(storage, ksdata, instClass) + ksdata.autopart.execute(storage, ksdata, instClass) + ksdata.partition.execute(storage, ksdata, instClass) + ksdata.raid.execute(storage, ksdata, instClass) + ksdata.volgroup.execute(storage, ksdata, instClass) + ksdata.logvol.execute(storage, ksdata, instClass) + ksdata.btrfs.execute(storage, ksdata, instClass) + storage.setUpBootLoader() + doPartitioning(storage) + diff --git a/pyanaconda/network.py b/pyanaconda/network.py index 859c36818..22025ff51 100644 --- a/pyanaconda/network.py +++ b/pyanaconda/network.py @@ -39,6 +39,7 @@ from flags import flags from simpleconfig import IfcfgFile from pyanaconda.constants import ROOT_PATH import urlgrabber.grabber +from pyanaconda.storage.devices import FcoeDiskDevice, iScsiDiskDevice import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -94,7 +95,7 @@ def sanityCheckHostname(hostname): return None # Try to determine what the hostname should be for this system -def getDefaultHostname(anaconda): +def getHostname(): resetResolver() hn = None @@ -114,14 +115,6 @@ def getDefaultHostname(anaconda): hn = hinfo[0] break - if hn and hn not in ('(none)', 'localhost', 'localhost.localdomain'): - return hn - - try: - hn = anaconda.network.hostname - except: - hn = None - if not hn or hn in ('(none)', 'localhost', 'localhost.localdomain'): hn = socket.gethostname() @@ -213,7 +206,6 @@ class NetworkDevice(IfcfgFile): def __init__(self, dir, iface): IfcfgFile.__init__(self, dir, iface) - self.description = "" if iface.startswith('ctc'): self.info["TYPE"] = "CTC" self.wepkey = "" @@ -340,27 +332,30 @@ class NetworkDevice(IfcfgFile): f.close() return content - def usedByFCoE(self, anaconda): - import storage - for d in anaconda.storage.devices: - if (isinstance(d, storage.devices.FcoeDiskDevice) and - d.nic == self.iface): - return True - return False + def setGateway(self, gw): + if ':' in gw: + self.set(('IPV6_DEFAULTGW', gw)) + else: + self.set(('GATEWAY', gw)) - def usedByRootOnISCSI(self, anaconda): - import storage - rootdev = anaconda.storage.rootDevice - for d in anaconda.storage.devices: - if (isinstance(d, storage.devices.iScsiDiskDevice) and - rootdev.dependsOn(d)): - if d.nic == "default": - if self.iface == ifaceForHostIP(d.host_address): - return True - elif d.nic == self.iface: - return True + def unsetDNS(self): + """Unset all DNS* ifcfg parameters.""" + i = 1 + while True: + if self.get("DNS%d" % i): + self.unset("DNS%d" %i) + else: + break + i += 1 - return False + def setDNS(self, ns): + dns = ns.split(',') + i = 1 + for addr in dns: + addr = addr.strip() + dnslabel = "DNS%d" % (i,) + self.set((dnslabel, addr)) + i += 1 class WirelessNetworkDevice(NetworkDevice): @@ -374,7 +369,6 @@ class WirelessNetworkDevice(NetworkDevice): self.info = dict() self.iface = iface self.dir = "" - self.description = "" def clear(self): self.info = dict() @@ -411,495 +405,153 @@ class Network: def __init__(self): - self.hostname = socket.gethostname() - - self.update() - - def update(self): - ifcfglog.debug("Network.update() called") - - self.netdevices = {} - self.ksdevice = None + ifcfglog.debug("Network object created called") + # TODO this may need to be handled in getDevices() if flags.imageInstall: return + # TODO this should go away (patch pending), + # default ifcfg files should be created in dracut + # populate self.netdevices devhash = isys.getDeviceProperties(dev=None) for iface in devhash.keys(): - if isys.isWirelessDevice(iface): - device = WirelessNetworkDevice(iface) - else: + if not isys.isWirelessDevice(iface): device = NetworkDevice(netscriptsDir, iface) - if os.access(device.path, os.R_OK): - device.loadIfcfgFile() - else: + if not os.access(device.path, os.R_OK): device.setDefaultConfig() - # TODORV - the last iface in loop wins, might be ok, - # not worthy of special juggling - if device.get('HOSTNAME'): - self.hostname = device.get('HOSTNAME') - - device.description = isys.getNetDevDesc(iface) - - self.netdevices[iface] = device - - - ksdevice = flags.cmdline.get('ksdevice', None) - if ksdevice: - bootif_mac = None - if ksdevice == 'bootif' and "BOOTIF" in flags.cmdline: - bootif_mac = flags.cmdline["BOOTIF"][3:].replace("-", ":").upper() - # sort for ksdevice=link (to select the same device as in initrd)) - for dev in sorted(self.netdevices): - mac = self.netdevices[dev].get('HWADDR').upper() - if ksdevice == 'link' and isys.getLinkStatus(dev): - self.ksdevice = dev - break - elif ksdevice == 'bootif': - if bootif_mac == mac: - self.ksdevice = dev - break - elif ksdevice == dev: - self.ksdevice = dev - break - elif ':' in ksdevice: - if ksdevice.upper() == mac: - self.ksdevice = dev - break - - - - def getDevice(self, device): - return self.netdevices[device] - - def getKSDevice(self): - if self.ksdevice is None: - return None - - try: - return self.netdevices[self.ksdevice] - except: - return None - - def setHostname(self, hn): - self.hostname = hn - if flags.imageInstall: - log.info("image install -- not setting hostname") - return - - log.info("setting installation environment hostname to %s" % hn) - iutil.execWithRedirect("hostname", ["-v", hn ], - stdout="/dev/tty5", stderr="/dev/tty5") - - def unsetDNS(self, devname): - """Unset all DNS* ifcfg parameters.""" - i = 1 - dev = self.netdevices[devname] - while True: - if dev.get("DNS%d" % i): - dev.unset("DNS%d" %i) - else: - break - i += 1 - - def setDNS(self, ns, device): - dns = ns.split(',') - i = 1 - for addr in dns: - addr = addr.strip() - dnslabel = "DNS%d" % (i,) - self.netdevices[device].set((dnslabel, addr)) - i += 1 - - def setGateway(self, gw, device): - if ':' in gw: - self.netdevices[device].set(('IPV6_DEFAULTGW', gw)) - else: - self.netdevices[device].set(('GATEWAY', gw)) - - @property - def gateway(self): - """GATEWAY - last device in list wins""" - for dev in reversed(self.netdevices.values()): - if (dev.get('GATEWAY') and - dev.get('DEFROUTE') != "no"): - return dev.get('GATEWAY') - return "" - - @property - def ipv6_defaultgw(self): - """IPV6_DEFAULTGW - last device in list wins""" - for dev in reversed(self.netdevices.values()): - if (dev.get('IPV6_DEFAULTGW') and - dev.get('DEFROUTE') != "no"): - return dev.get('IPV6_DEFAULTGW') - return "" - - def lookupHostname(self): - # can't look things up if they don't exist! - if not self.hostname or self.hostname == "localhost.localdomain": - return None - - if not hasActiveNetDev(): - log.warning("no network devices were available to look up host name") - return None +def getDevices(): + # TODO: filter with existence of ifcfg file? + return isys.getDeviceProperties().keys() - try: - (family, socktype, proto, canonname, sockaddr) = \ - socket.getaddrinfo(self.hostname, None, socket.AF_INET)[0] - (ip, port) = sockaddr - except: - try: - (family, socktype, proto, canonname, sockaddr) = \ - socket.getaddrinfo(self.hostname, None, socket.AF_INET6)[0] - (ip, port, flowinfo, scopeid) = sockaddr - except: - return None - - return ip - - # Note that the file is written-out only if there is a value - # that has changed. - def writeIfcfgFiles(self): - for device in self.netdevices.values(): - device.writeIfcfgFile() - - # devices == None => set for all - def updateActiveDevices(self, devices=None): - for devname, device in self.netdevices.items(): - if devices and devname not in devices: - device.set(('ONBOOT', 'no')) - else: - device.set(('ONBOOT', 'yes')) - - def getOnbootControlledIfaces(self): - ifaces = [] - for iface, device in self.netdevices.items(): - if (device.get('ONBOOT') == "yes" and - device.get('NM_CONTROLLED') == "yes"): - ifaces.append(iface) - return ifaces - - def writeSSIDifcfgs(self, devssids): - ssids = [] - for ssidlist in devssids.values(): - ssids.extend(ssidlist) - for ssid in ssids: - path = "{0}/ifcfg-{1}".format(netscriptsDir, ssid) - ifcfgfile = open(path, "w") - ifcfgfile.write("NAME={0}\n".format(ssid)+ - "TYPE=Wireless\n"+ - "ESSID={0}\n".format(ssid)+ - "NM_CONTROLLED=yes\n") - ifcfgfile.close() - - - def getSSIDs(self): - return getSSIDs() - - def writeKS(self, f): - devNames = self.netdevices.keys() - devNames.sort() - - if len(devNames) == 0: - return - - for devName in devNames: - dev = self.netdevices[devName] - line = "%s" % kickstartNetworkData(dev, self.hostname) - f.write(line) - - def hasNameServers(self, hash): - if hash.keys() == []: - return False - - for key in hash.keys(): - if key.upper().startswith('DNS'): - return True +def waitForDevicesActivation(devices): + waited_devs_props = {} - return False + bus = dbus.SystemBus() + nm = bus.get_object(isys.NM_SERVICE, isys.NM_MANAGER_PATH) + device_paths = nm.get_dbus_method("GetDevices")() + for device_path in device_paths: + device = bus.get_object(isys.NM_SERVICE, device_path) + device_props_iface = dbus.Interface(device, isys.DBUS_PROPS_IFACE) + iface = str(device_props_iface.Get(isys.NM_DEVICE_IFACE, "Interface")) + if iface in devices: + waited_devs_props[iface] = device_props_iface + + i = 0 + while True: + for dev, device_props_iface in waited_devs_props.items(): + state = device_props_iface.Get(isys.NM_DEVICE_IFACE, "State") + if state == isys.NM_DEVICE_STATE_ACTIVATED: + waited_devs_props.pop(dev) + if len(waited_devs_props) == 0 or i >= CONNECTION_TIMEOUT: + break + i += 1 + time.sleep(1) + + return waited_devs_props.keys() + +# write out current configuration state and wait for NetworkManager +# to bring the device up, watch NM state and return to the caller +# once we have a state +def waitForConnection(): + bus = dbus.SystemBus() + nm = bus.get_object(isys.NM_SERVICE, isys.NM_MANAGER_PATH) + props = dbus.Interface(nm, isys.DBUS_PROPS_IFACE) - def hasWirelessDev(self): - for dev in self.netdevices: - if isys.isWirelessDevice(dev): - return True - return False + i = 0 + while i < CONNECTION_TIMEOUT: + state = props.Get(isys.NM_SERVICE, "State") + if nmIsConnected(state): + return True + i += 1 + time.sleep(1) - def _copyFileToPath(self, file, instPath='', overwrite=False): - if not os.path.isfile(file): - return False - destfile = os.path.join(instPath, file.lstrip('/')) - if (os.path.isfile(destfile) and not overwrite): - return False - if not os.path.isdir(os.path.dirname(destfile)): - iutil.mkdirChain(os.path.dirname(destfile)) - shutil.copy(file, destfile) + state = props.Get(isys.NM_SERVICE, "State") + if nmIsConnected(state): return True - def _copyIfcfgFiles(self): - files = os.listdir(netscriptsDir) - for cfgFile in files: - if cfgFile.startswith(("ifcfg-","keys-")): - srcfile = os.path.join(netscriptsDir, cfgFile) - self._copyFileToPath(srcfile, ROOT_PATH) + return False - def copyConfigToPath(self): - if flags.imageInstall: - # for image installs we only want to write out - # /etc/sysconfig/network - destfile = os.path.normpath(ROOT_PATH + networkConfFile) - if not os.path.isdir(os.path.dirname(destfile)): - iutil.mkdirChain(os.path.dirname(destfile)) - shutil.move("/tmp/sysconfig-network", destfile) - return +# get a kernel cmdline string for dracut needed for access to storage host +def dracutSetupArgs(networkStorageDevice): - # /etc/sysconfig/network-scripts/ifcfg-* - # /etc/sysconfig/network-scripts/keys-* - # we can copy all of them - self._copyIfcfgFiles() + if networkStorageDevice.nic == "default": + nic = ifaceForHostIP(networkStorageDevice.host_address) + if not nic: + return "" + else: + nic = networkStorageDevice.nic - # /etc/dhcp/dhclient-DEVICE.conf - # TODORV: do we really don't want overwrite on live cd? - for devName, device in self.netdevices.items(): - dhclientfile = os.path.join("/etc/dhcp/dhclient-%s.conf" % devName) - self._copyFileToPath(dhclientfile, ROOT_PATH) + if nic not in getDevices(): + log.error('Unknown network interface: %s' % nic) + return "" - # /etc/sysconfig/network - self._copyFileToPath(networkConfFile, ROOT_PATH, - overwrite=flags.livecdInstall) - - # /etc/resolv.conf - self._copyFileToPath("/etc/resolv.conf", ROOT_PATH, - overwrite=flags.livecdInstall) - - # /etc/udev/rules.d/70-persistent-net.rules - self._copyFileToPath("/etc/udev/rules.d/70-persistent-net.rules", - ROOT_PATH, overwrite=flags.livecdInstall) - - self._copyFileToPath(ipv6ConfFile, ROOT_PATH, - overwrite=flags.livecdInstall) - - def disableNMForStorageDevices(self, anaconda): - for devName, device in self.netdevices.items(): - if (device.usedByFCoE(anaconda) or - device.usedByRootOnISCSI(anaconda)): - dev = NetworkDevice(ROOT_PATH + netscriptsDir, devName) - if os.access(dev.path, os.R_OK): - dev.loadIfcfgFile() - dev.set(('NM_CONTROLLED', 'no')) - dev.writeIfcfgFile() - log.info("network device %s used by storage will not be " - "controlled by NM" % device.path) - else: - log.warning("disableNMForStorageDevices: %s file not found" % - device.path) - - def autostartFCoEDevices(self, anaconda): - for devName, device in self.netdevices.items(): - if device.usedByFCoE(anaconda): - dev = NetworkDevice(ROOT_PATH + netscriptsDir, devName) - if os.access(dev.path, os.R_OK): - dev.loadIfcfgFile() - dev.set(('ONBOOT', 'yes')) - dev.writeIfcfgFile() - log.debug("setting ONBOOT=yes for network device %s used by fcoe" - % device.path) + ifcfg = NetworkDevice(netscriptsDir, nic) + ifcfg.loadIfcfgFile() + return dracutBootArguments(ifcfg, + networkStorageDevice.host_address, + getHostname()) + +def dracutBootArguments(ifcfg, storage_ipaddr, hostname=None): + + netargs = set() + devname = ifcfg.iface + + if ifcfg.get('BOOTPROTO') == 'ibft': + netargs.add("ip=ibft") + elif storage_ipaddr: + if hostname is None: + hostname = "" + # if using ipv6 + if ':' in storage_ipaddr: + if ifcfg.get('DHCPV6C') == "yes": + # XXX combination with autoconf not yet clear, + # support for dhcpv6 is not yet implemented in NM/ifcfg-rh + netargs.add("ip=%s:dhcp6" % devname) + elif ifcfg.get('IPV6_AUTOCONF') == "yes": + netargs.add("ip=%s:auto6" % devname) + elif ifcfg.get('IPV6ADDR'): + ipaddr = "[%s]" % ifcfg.get('IPV6ADDR') + if ifcfg.get('IPV6_DEFAULTGW'): + gateway = "[%s]" % ifcfg.get('IPV6_DEFAULTGW') else: - log.warning("autoconnectFCoEDevices: %s file not found" % - device.path) - - def write(self): - ifcfglog.debug("Network.write() called") - - # /etc/sysconfig/network - if flags.imageInstall: - # don't write files into host's /etc/sysconfig on image installs - newnetwork = "/tmp/sysconfig-network" - else: - newnetwork = "%s.new" % (networkConfFile) - - f = open(newnetwork, "w") - f.write("NETWORKING=yes\n") - f.write("HOSTNAME=") - - # use instclass hostname if set(kickstart) to override - if self.hostname: - f.write(self.hostname + "\n") - else: - f.write("localhost.localdomain\n") - - if self.gateway: - f.write("GATEWAY=%s\n" % self.gateway) - - if self.ipv6_defaultgw: - f.write("IPV6_DEFAULTGW=%s\n" % self.ipv6_defaultgw) - - f.close() - if flags.imageInstall: - # for image installs, all we want to write out is the contents of - # /etc/sysconfig/network - ifcfglog.debug("not writing per-device configs for image install") - return + gateway = "" + netargs.add("ip=%s::%s:%s:%s:%s:none" % (ipaddr, gateway, + ifcfg.get('PREFIX'), hostname, devname)) else: - shutil.move(newnetwork, networkConfFile) - - devices = self.netdevices.values() - - # /etc/sysconfig/network-scripts/ifcfg-* - # /etc/sysconfig/network-scripts/keys-* - for dev in devices: - - dev.writeIfcfgFile() - - # /etc/resolv.conf is managed by NM - - # disable ipv6 - if ('noipv6' in flags.cmdline - and not [dev for dev in devices - if dev.get('IPV6INIT') == "yes"]): - if os.path.exists(ipv6ConfFile): - log.warning('Not disabling ipv6, %s exists' % ipv6ConfFile) + if ifcfg.get('bootproto').lower() == 'dhcp': + netargs.add("ip=%s:dhcp" % devname) else: - log.info('Disabling ipv6 on target system') - f = open(ipv6ConfFile, "w") - f.write("# Anaconda disabling ipv6\n") - f.write("options ipv6 disable=1\n") - f.close() - - def waitForDevicesActivation(self, devices): - waited_devs_props = {} - - bus = dbus.SystemBus() - nm = bus.get_object(isys.NM_SERVICE, isys.NM_MANAGER_PATH) - device_paths = nm.get_dbus_method("GetDevices")() - for device_path in device_paths: - device = bus.get_object(isys.NM_SERVICE, device_path) - device_props_iface = dbus.Interface(device, isys.DBUS_PROPS_IFACE) - iface = str(device_props_iface.Get(isys.NM_DEVICE_IFACE, "Interface")) - if iface in devices: - waited_devs_props[iface] = device_props_iface - - i = 0 - while True: - for dev, device_props_iface in waited_devs_props.items(): - state = device_props_iface.Get(isys.NM_DEVICE_IFACE, "State") - if state == isys.NM_DEVICE_STATE_ACTIVATED: - waited_devs_props.pop(dev) - if len(waited_devs_props) == 0 or i >= CONNECTION_TIMEOUT: - break - i += 1 - time.sleep(1) - - return waited_devs_props.keys() - - # write out current configuration state and wait for NetworkManager - # to bring the device up, watch NM state and return to the caller - # once we have a state - def waitForConnection(self): - bus = dbus.SystemBus() - nm = bus.get_object(isys.NM_SERVICE, isys.NM_MANAGER_PATH) - props = dbus.Interface(nm, isys.DBUS_PROPS_IFACE) - - i = 0 - while i < CONNECTION_TIMEOUT: - state = props.Get(isys.NM_SERVICE, "State") - if nmIsConnected(state): - return True - i += 1 - time.sleep(1) - - state = props.Get(isys.NM_SERVICE, "State") - if nmIsConnected(state): - return True - - return False - - # write out current configuration state and wait for NetworkManager - # to bring the device up, watch NM state and return to the caller - # once we have a state - def bringUp(self): - self.write() - if self.waitForConnection(): - resetResolver() - return True - else: - return False + if ifcfg.get('GATEWAY'): + gateway = ifcfg.get('GATEWAY') + else: + gateway = "" - # get a kernel cmdline string for dracut needed for access to host host - def dracutSetupArgs(self, networkStorageDevice): - netargs=set() + netmask = ifcfg.get('netmask') + prefix = ifcfg.get('prefix') + if not netmask and prefix: + netmask = isys.prefix2netmask(int(prefix)) - if networkStorageDevice.nic == "default": - nic = ifaceForHostIP(networkStorageDevice.host_address) - if not nic: - return "" - else: - nic = networkStorageDevice.nic + netargs.add("ip=%s::%s:%s:%s:%s:none" % (ifcfg.get('ipaddr'), + gateway, netmask, hostname, devname)) - if nic not in self.netdevices.keys(): - log.error('Unknown network interface: %s' % nic) - return "" + hwaddr = ifcfg.get("HWADDR") + if hwaddr: + netargs.add("ifname=%s:%s" % (devname, hwaddr.lower())) - dev = self.netdevices[nic] + nettype = ifcfg.get("NETTYPE") + subchannels = ifcfg.get("SUBCHANNELS") + if iutil.isS390() and nettype and subchannels: + znet = "rd.znet=%s,%s" % (nettype, subchannels) + options = ifcfg.get("OPTIONS").strip("'\"") + if options: + options = filter(lambda x: x != '', options.split(' ')) + znet += ",%s" % (','.join(options)) + netargs.add(znet) - if dev.get('BOOTPROTO') == 'ibft': - netargs.add("ip=ibft") - elif networkStorageDevice.host_address: - if self.hostname: - hostname = self.hostname - else: - hostname = "" - - # if using ipv6 - if ':' in networkStorageDevice.host_address: - if dev.get('DHCPV6C') == "yes": - # XXX combination with autoconf not yet clear, - # support for dhcpv6 is not yet implemented in NM/ifcfg-rh - netargs.add("ip=%s:dhcp6" % nic) - elif dev.get('IPV6_AUTOCONF') == "yes": - netargs.add("ip=%s:auto6" % nic) - elif dev.get('IPV6ADDR'): - ipaddr = "[%s]" % dev.get('IPV6ADDR') - if dev.get('IPV6_DEFAULTGW'): - gateway = "[%s]" % dev.get('IPV6_DEFAULTGW') - else: - gateway = "" - netargs.add("ip=%s::%s:%s:%s:%s:none" % (ipaddr, gateway, - dev.get('PREFIX'), hostname, nic)) - else: - if dev.get('bootproto').lower() == 'dhcp': - netargs.add("ip=%s:dhcp" % nic) - else: - if dev.get('GATEWAY'): - gateway = dev.get('GATEWAY') - else: - gateway = "" - - netmask = dev.get('netmask') - prefix = dev.get('prefix') - if not netmask and prefix: - netmask = isys.prefix2netmask(int(prefix)) - - netargs.add("ip=%s::%s:%s:%s:%s:none" % (dev.get('ipaddr'), - gateway, netmask, hostname, nic)) - - hwaddr = dev.get("HWADDR") - if hwaddr: - netargs.add("ifname=%s:%s" % (nic, hwaddr.lower())) - - nettype = dev.get("NETTYPE") - subchannels = dev.get("SUBCHANNELS") - if iutil.isS390() and nettype and subchannels: - znet = "rd.znet=%s,%s" % (nettype, subchannels) - options = dev.get("OPTIONS").strip("'\"") - if options: - options = filter(lambda x: x != '', options.split(' ')) - znet += ",%s" % (','.join(options)) - netargs.add(znet) - - return netargs + return netargs def kickstartNetworkData(ifcfg, hostname=None): @@ -1037,3 +689,206 @@ def resetResolver(): isys.resetResolv() urlgrabber.grabber.reset_curl_obj() +def setHostname(hn): + if flags.imageInstall: + log.info("image install -- not setting hostname") + return + + log.info("setting installation environment hostname to %s" % hn) + iutil.execWithRedirect("hostname", ["-v", hn ], + stdout="/dev/tty5", stderr="/dev/tty5") + +def _copyFileToPath(file, destPath='', overwrite=False): + if not os.path.isfile(file): + return False + destfile = os.path.join(destPath, file.lstrip('/')) + if (os.path.isfile(destfile) and not overwrite): + return False + if not os.path.isdir(os.path.dirname(destfile)): + iutil.mkdirChain(os.path.dirname(destfile)) + shutil.copy(file, destfile) + return True + +def _copyIfcfgFiles(destPath): + files = os.listdir(netscriptsDir) + for cfgFile in files: + if cfgFile.startswith(("ifcfg-","keys-")): + srcfile = os.path.join(netscriptsDir, cfgFile) + _copyFileToPath(srcfile, destPath) + +def copyConfigToPath(destPath): + if flags.imageInstall: + # for image installs we only want to write out + # /etc/sysconfig/network + destfile = os.path.normpath(destPath + networkConfFile) + if not os.path.isdir(os.path.dirname(destfile)): + iutil.mkdirChain(os.path.dirname(destfile)) + shutil.move("/tmp/sysconfig-network", destfile) + return + + # /etc/sysconfig/network-scripts/ifcfg-* + # /etc/sysconfig/network-scripts/keys-* + # we can copy all of them + _copyIfcfgFiles(destPath) + + # /etc/dhcp/dhclient-DEVICE.conf + # TODORV: do we really don't want overwrite on live cd? + for devName in getDevices(): + dhclientfile = os.path.join("/etc/dhcp/dhclient-%s.conf" % devName) + _copyFileToPath(dhclientfile, destPath) + + # /etc/sysconfig/network + _copyFileToPath(networkConfFile, destPath, + overwrite=flags.livecdInstall) + + # /etc/resolv.conf + _copyFileToPath("/etc/resolv.conf", destPath, + overwrite=flags.livecdInstall) + + # /etc/udev/rules.d/70-persistent-net.rules + _copyFileToPath("/etc/udev/rules.d/70-persistent-net.rules", + destPath, overwrite=flags.livecdInstall) + + _copyFileToPath(ipv6ConfFile, destPath, + overwrite=flags.livecdInstall) + +def get_ksdevice_name(ksspec=""): + + if not ksspec: + ksspec = flags.cmdline.get('ksdevice', "") + ksdevice = ksspec + + bootif_mac = None + if ksdevice == 'bootif' and "BOOTIF" in flags.cmdline: + bootif_mac = flags.cmdline["BOOTIF"][3:].replace("-", ":").upper() + for dev in sorted(getDevices()): + # "eth0" + if ksdevice == dev: + break + # "link" + elif ksdevice == 'link' and isys.getLinkStatus(dev): + ksdevice = dev + break + # "XX:XX:XX:XX:XX:XX" (mac address) + elif ':' in ksdevice: + if ksdevice.upper() == isys.getMacAddress(dev): + ksdevice = dev + break + # "bootif" and BOOTIF==XX:XX:XX:XX:XX:XX + elif ksdevice == 'bootif': + if bootif_mac == isys.getMacAddress(dev): + ksdevice = dev + break + + return ksdevice + +# note that NetworkDevice.get returns "" if key is not found +def get_ifcfg_value(iface, key, root_path=""): + dev = NetworkDevice(os.path.normpath(root_path + netscriptsDir), iface) + dev.loadIfcfgFile() + return dev.get(key) + +def write_sysconfig_network(): + + if flags.imageInstall: + # don't write files into host's /etc/sysconfig on image installs + newnetwork = "/tmp/sysconfig-network" + else: + newnetwork = "%s.new" % (networkConfFile) + + f = open(newnetwork, "w") + f.write("# Generated by anaconda") + f.write("NETWORKING=yes\n") + f.write("HOSTNAME=") + + hostname = getHostname() + if hostname: + f.write(hostname + "\n") + else: + f.write("localhost.localdomain\n") + + gateway = ipv6_defaultgw = None + for iface in reversed(getDevices()): + dev = NetworkDevice(netscriptsDir, iface) + dev.loadIfcfgFile() + if dev.get('DEFROUTE') != "no": + continue + if dev.get('GATEWAY'): + gateway = dev.get('GATEWAY') + if dev.get('IPV6_DEFAULTGW'): + ipv6_defaultgw = dev.get('IPV6_DEFAULTGW') + if gateway and ipv6_defaultgw: + break + + if gateway: + f.write("GATEWAY=%s\n" % gateway) + + if ipv6_defaultgw: + f.write("IPV6_DEFAULTGW=%s\n" % ipv6_defaultgw) + f.close() + + if not flags.imageInstall: + shutil.move(newnetwork, networkConfFile) + +# TODO: do it right in sysroot (instead of copying the files later)? +def disableIPV6(): + if ('noipv6' in flags.cmdline + and not any(get_ifcfg_value(dev, 'IPV6INIT') == "yes" + for dev in getDevices())): + if os.path.exists(ipv6ConfFile): + log.warning('Not disabling ipv6, %s exists' % ipv6ConfFile) + else: + log.info('Disabling ipv6 on target system') + f = open(ipv6ConfFile, "w") + f.write("# Anaconda disabling ipv6\n") + f.write("options ipv6 disable=1\n") + f.close() + +def disableNMForStorageDevices(storage): + for devname in getDevices(): + if (usedByFCoE(devname, storage) or + usedByRootOnISCSI(devname, storage)): + dev = NetworkDevice(ROOT_PATH + netscriptsDir, devname) + if os.access(dev.path, os.R_OK): + dev.loadIfcfgFile() + dev.set(('NM_CONTROLLED', 'no')) + dev.writeIfcfgFile() + log.info("network device %s used by storage will not be " + "controlled by NM" % devname) + else: + log.warning("disableNMForStorageDevices: ifcfg file for %s not found" % + devname) + +def autostartFCoEDevices(storage): + for devname in getDevices(): + if usedByFCoE(devname, storage): + dev = NetworkDevice(ROOT_PATH + netscriptsDir, devname) + if os.access(dev.path, os.R_OK): + dev.loadIfcfgFile() + dev.set(('ONBOOT', 'yes')) + dev.writeIfcfgFile() + log.debug("setting ONBOOT=yes for network device %s used by fcoe" + % devname) + else: + log.warning("autoconnectFCoEDevices: ifcfg file for %s not found" % + devname) + +def usedByFCoE(iface, storage): + for d in storage.devices: + if (isinstance(d, FcoeDiskDevice) and + d.nic == iface): + return True + return False + +def usedByRootOnISCSI(iface, storage): + rootdev = storage.rootDevice + for d in storage.devices: + if (isinstance(d, iScsiDiskDevice) and + rootdev.dependsOn(d)): + if d.nic == "default": + if iface == ifaceForHostIP(d.host_address): + return True + elif d.nic == iface: + return True + + return False diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py index ca23f53a1..8c5a5606c 100644 --- a/pyanaconda/packaging/__init__.py +++ b/pyanaconda/packaging/__init__.py @@ -48,9 +48,7 @@ from pyanaconda.iutil import ProxyString, ProxyStringError from pykickstart.parser import Group import logging -log = logging.getLogger("anaconda") - -from pyanaconda.backend_log import log as instlog +log = logging.getLogger("packaging") from pyanaconda.errors import * #from pyanaconda.progress import progress @@ -169,7 +167,9 @@ class Payload(object): def _repoNeedsNetwork(self, repo): """ Returns True if the ksdata repo requires networking. """ - urls = [repo.baseurl] + repo.mirrorlist + urls = [repo.baseurl] + if repo.mirrorlist: + urls.extend(repo.mirrorlist) network_protocols = ["http:", "ftp:", "nfs:", "nfsiso:"] for url in urls: if any([url.startswith(p) for p in network_protocols]): @@ -236,6 +236,9 @@ class Payload(object): def groups(self): raise NotImplementedError() + def languageGroups(self, lang): + raise NotImplementedError() + def description(self, groupid): raise NotImplementedError() @@ -466,34 +469,16 @@ class Payload(object): ### ### METHODS FOR INSTALLING THE PAYLOAD ### - def preInstall(self, packages=None): + def preInstall(self, packages=None, groups=None): """ Perform pre-installation tasks. """ iutil.mkdirChain(ROOT_PATH + "/root") - if self.data.upgrade.upgrade: - mode = "upgrade" - else: - mode = "install" - - log_file_name = "%s.log" % mode - log_file_path = "%s/root/%s" % (ROOT_PATH, log_file_name) - try: - shutil.rmtree (log_file_path) - except OSError: - pass - - self.install_log = open(log_file_path, "w+") - - syslogname = "%s.syslog" % log_file_path - try: - shutil.rmtree (syslogname) - except OSError: - pass - instlog.start(ROOT_PATH, syslogname) - - if packages is not None: + if packages: map(self.selectPackage, packages) + if groups: + map(self.selectGroup, groups) + def install(self): """ Install the payload. """ raise NotImplementedError() @@ -593,10 +578,6 @@ class Payload(object): self._copyDriverDiskFiles() - # stop logger - instlog.stop() - - class ImagePayload(Payload): """ An ImagePayload installs an OS image to the target system. """ def __init__(self, data): diff --git a/pyanaconda/packaging/yumpayload.py b/pyanaconda/packaging/yumpayload.py index 8aea2fb57..f6f940a44 100644 --- a/pyanaconda/packaging/yumpayload.py +++ b/pyanaconda/packaging/yumpayload.py @@ -42,9 +42,13 @@ import os import shutil import time +import tempfile from . import * +import logging +log = logging.getLogger("packaging") + try: import rpm except ImportError: @@ -72,9 +76,6 @@ from pyanaconda.image import findFirstIsoImage import gettext _ = lambda x: gettext.ldgettext("anaconda", x) -import logging -log = logging.getLogger("anaconda") - from pyanaconda.errors import * from pyanaconda.packaging import NoSuchGroup, NoSuchPackage import pyanaconda.progress as progress @@ -153,7 +154,7 @@ class YumPayload(PackagePayload): # have group info ready. self.gatherRepoMetadata() - def _resetYum(self, root=None): + def _resetYum(self, root=None, keep_cache=False): """ Delete and recreate the payload's YumBase instance. """ import shutil if root is None: @@ -161,10 +162,11 @@ class YumPayload(PackagePayload): with _yum_lock: if self._yum: - for repo in self._yum.repos.listEnabled(): - if repo.name == BASE_REPO_NAME and \ - os.path.isdir(repo.cachedir): - shutil.rmtree(repo.cachedir) + if not keep_cache: + for repo in self._yum.repos.listEnabled(): + if repo.name == BASE_REPO_NAME and \ + os.path.isdir(repo.cachedir): + shutil.rmtree(repo.cachedir) del self._yum @@ -287,7 +289,7 @@ reposdir=%s releasever = self._yum.conf.yumvar['releasever'] self._writeYumConfig() - self._resetYum(root=ROOT_PATH) + self._resetYum(root=ROOT_PATH, keep_cache=True) log.debug("setting releasever to previous value of %s" % releasever) self._yum.preconf.releasever = releasever @@ -737,6 +739,16 @@ reposdir=%s return groups + def languageGroups(self, lang): + groups = [] + yum_groups = self._yumGroups + + if yum_groups: + with _yum_lock: + groups = [g.groupid for g in yum_groups.get_groups() if g.langonly == lang] + + return groups + def description(self, groupid): """ Return name/description tuple for the group specified by id. """ groups = self._yumGroups @@ -950,9 +962,9 @@ reposdir=%s if not selected: log.error("failed to select a kernel from %s" % kernels) - def preInstall(self, packages=None): + def preInstall(self, packages=None, groups=None): """ Perform pre-installation tasks. """ - super(YumPayload, self).preInstall(packages=packages) + super(YumPayload, self).preInstall(packages=packages, groups=groups) progress.send_message(_("Starting package installation process")) if self.install_device: @@ -1001,12 +1013,13 @@ reposdir=%s self._yum.ts.order() self._yum.ts.clean() - # set up rpm logging to go to our log - self._yum.ts.ts.scriptFd = self.install_log.fileno() - rpm.setLogFile(self.install_log) + # Write scriptlet output to a file to be logged later + script_log = tempfile.NamedTemporaryFile(delete=False) + self._yum.ts.ts.scriptFd = script_log.fileno() + rpm.setLogFile(script_log) # create the install callback - rpmcb = RPMCallback(self._yum, self.install_log, + rpmcb = RPMCallback(self._yum, script_log, upgrade=self.data.upgrade.upgrade) if flags.testing: @@ -1037,12 +1050,19 @@ reposdir=%s raise exn else: log.info("transaction complete") - self.install_log.write("*** FINISHED INSTALLING PACKAGES ***") progress.send_step() finally: - self.install_log.close() self._yum.ts.close() iutil.resetRpmDb() + script_log.close() + + # log the contents of the scriptlet logfile + log.info("==== start rpm scriptlet logs ====") + with open(script_log.name) as f: + for l in f: + log.info(l) + log.info("==== end rpm scriptlet logs ====") + os.unlink(script_log.name) def postInstall(self): """ Perform post-installation tasks. """ @@ -1070,7 +1090,7 @@ reposdir=%s class RPMCallback(object): def __init__(self, yb, log, upgrade=False): self._yum = yb # yum.YumBase - self.install_log = log # file instance + self.install_log = log # logfile for yum script logs self.upgrade = upgrade # boolean self.package_file = None # file instance (package file management) @@ -1132,9 +1152,10 @@ class RPMCallback(object): log_msg = msg_format % (mode, txmbr.po, self.completed_actions, self.total_actions) - self.install_log.write("%s %s\n" % (time.strftime("%H:%M:%S"), - log_msg)) + log.info(log_msg) + self.install_log.write(log_msg+"\n") self.install_log.flush() + progress.send_message(progress_msg) self.package_file = None diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py index 5854e25a1..be84c1a2e 100644 --- a/pyanaconda/rescue.py +++ b/pyanaconda/rescue.py @@ -23,7 +23,6 @@ from snack import * from constants import * from textw.constants_text import * -from textw.add_drive_text import addDriveDialog from text import WaitWindow, OkCancelWindow, ProgressWindow, PassphraseEntryWindow, stepToClasses from flags import flags import sys @@ -92,11 +91,11 @@ class RescueInterface(InstallInterfaceBase): else: return OkCancelWindow(self.screen, title, text) - def enableNetwork(self, anaconda): - if len(anaconda.network.netdevices) == 0: + def enableNetwork(self): + if len(network.getDevices()) == 0: return False from textw.netconfig_text import NetworkConfiguratorText - w = NetworkConfiguratorText(self.screen, anaconda) + w = NetworkConfiguratorText(self.screen, self) ret = w.run() return ret != INSTALL_BACK @@ -115,9 +114,6 @@ class RescueInterface(InstallInterfaceBase): def resume(self): pass - def run(self, anaconda): - self.anaconda = anaconda - def __init__(self): InstallInterfaceBase.__init__(self) self.screen = SnackScreen() @@ -171,18 +167,6 @@ def makeResolvConf(instPath): f.write(buf) f.close() -# -# Write out something useful for networking and start interfaces -# -def startNetworking(network, intf): - # do lo first - if os.system("/usr/sbin/ifconfig lo 127.0.0.1"): - log.error("Error trying to start lo in rescue.py::startNetworking()") - - # start up dhcp interfaces first - if not network.bringUp(): - log.error("Error bringing up network interfaces") - def runShell(screen = None, msg=""): if screen: screen.suspend() @@ -204,7 +188,7 @@ def runShell(screen = None, msg=""): if os.path.exists("/usr/bin/firstaidkit-qs"): proc = subprocess.Popen(["/usr/bin/firstaidkit-qs"]) proc.wait() - + if proc is None or proc.returncode!=0: if os.path.exists("/bin/bash"): iutil.execConsole() @@ -215,7 +199,9 @@ def runShell(screen = None, msg=""): if screen: screen.finish() -def doRescue(anaconda): +def doRescue(rescue_mount, ksdata, platform): + import storage + for file in [ "services", "protocols", "group", "joe", "man.config", "nsswitch.conf", "selinux", "mke2fs.conf" ]: try: @@ -223,107 +209,82 @@ def doRescue(anaconda): except OSError: pass + intf = RescueInterface() + # see if they would like networking enabled if not network.hasActiveNetDev(): + rc = ButtonChoiceWindow(intf.screen, _("Setup Networking"), + _("Do you want to start the network interfaces on " + "this system?"), [_("Yes"), _("No")]) - while True: - rc = ButtonChoiceWindow(anaconda.intf.screen, _("Setup Networking"), - _("Do you want to start the network interfaces on " - "this system?"), [_("Yes"), _("No")]) - - if rc != _("No").lower(): - if not anaconda.intf.enableNetwork(anaconda): - anaconda.intf.messageWindow(_("No Network Available"), - _("Unable to activate a networking device. Networking " - "will not be available in rescue mode.")) - break - - startNetworking(anaconda.network, anaconda.intf) - break - else: - break - - # shutdown the interface now - anaconda.intf.shutdown() - anaconda.intf = None + if rc != _("No").lower(): + if not intf.enableNetwork(): + intf.messageWindow(_("No Network Available"), + _("Unable to activate a networking device. Networking " + "will not be available in rescue mode.")) # Early shell access with no disk access attempts - if not anaconda.rescue_mount: + if not rescue_mount: # the %post should be responsible for mounting all needed file systems # NOTE: 1st script must be bash or simple python as nothing else might be available in the rescue image - if anaconda.ksdata and anaconda.ksdata.scripts: + if flags.automatedInstall and ksdata.scripts: from kickstart import runPostScripts - runPostScripts(anaconda.ksdata.scripts) + runPostScripts(ksdata.scripts) else: runShell() sys.exit(0) - anaconda.intf = RescueInterface() - - if anaconda.ksdata: - if anaconda.ksdata.rescue and anaconda.ksdata.rescue.romount: - readOnly = 1 - else: - readOnly = 0 + if flags.automatedInstall: + readOnly = ksdata.rescue.romount else: # prompt to see if we should try and find root filesystem and mount # everything in /etc/fstab on that root while True: - rc = ButtonChoiceWindow(anaconda.intf.screen, _("Rescue"), + rc = ButtonChoiceWindow(intf.screen, _("Rescue"), _("The rescue environment will now attempt to find your " "Linux installation and mount it under the directory " "%s. You can then make any changes required to your " "system. If you want to proceed with this step choose " "'Continue'. You can also choose to mount your file systems " "read-only instead of read-write by choosing 'Read-Only'. " - "If you need to activate SAN devices choose 'Advanced'." "\n\n" "If for some reason this process fails you can choose 'Skip' " "and this step will be skipped and you will go directly to a " "command shell.\n\n") % (ROOT_PATH,), - [_("Continue"), _("Read-Only"), _("Skip"), _("Advanced")] ) + [_("Continue"), _("Read-Only"), _("Skip")] ) if rc == _("Skip").lower(): - runShell(anaconda.intf.screen) + runShell(intf.screen) sys.exit(0) - elif rc == _("Advanced").lower(): - addDialog = addDriveDialog(anaconda) - addDialog.addDriveDialog(anaconda.intf.screen) - continue - elif rc == _("Read-Only").lower(): - readOnly = 1 else: - readOnly = 0 - break + readOnly = rc == _("Read-Only").lower() - import storage - storage.storageInitialize(anaconda) + break - (disks, notUpgradable) = storage.findExistingRootDevices(anaconda, upgradeany=True) + sto = storage.Storage(ksdata, platform) + storage.storageInitialize(sto, ksdata, []) + roots = storage.findExistingInstallations(sto.devicetree) - if not disks: + if not roots: root = None - elif (len(disks) == 1) or anaconda.ksdata: - root = disks[0] + elif len(roots) == 1 or ksdata.upgrade.upgrade: + root = roots[0] else: - height = min (len (disks), 12) + height = min (len (roots), 12) if height == 12: scroll = 1 else: scroll = 0 - devList = [] - for (device, relstr) in disks: - if getattr(device.format, "label", None): - devList.append("%s (%s) - %s" % (device.name, device.format.label, relstr)) - else: - devList.append("%s - %s" % (device.name, relstr)) + lst = [] + for root in roots: + lst.append("%s" % root.name) (button, choice) = \ - ListboxChoiceWindow(anaconda.intf.screen, _("System to Rescue"), + ListboxChoiceWindow(intf.screen, _("System to Rescue"), _("Which device holds the root partition " - "of your installation?"), devList, + "of your installation?"), lst, [ _("OK"), _("Exit") ], width = 30, scroll = scroll, height = height, help = "multipleroot") @@ -331,15 +292,15 @@ def doRescue(anaconda): if button == _("Exit").lower(): root = None else: - root = disks[choice] + root = roots[choice] - rootmounted = 0 + rootmounted = False if root: try: # TODO: add a callback to warn about dirty filesystems - rc = mountExistingSystem(anaconda.storage.fsset, root, - allowDirty = 1, + rc = mountExistingSystem(sto.fsset, root.device, + allowDirty = True, readOnly = readOnly) if not flags.imageInstall: @@ -350,20 +311,20 @@ def doRescue(anaconda): "when you are finished.") % ANACONDA_CLEANUP if rc == -1: - if anaconda.ksdata: + if flags.automatedInstall: log.error("System had dirty file systems which you chose not to mount") else: - ButtonChoiceWindow(anaconda.intf.screen, _("Rescue"), + ButtonChoiceWindow(intf.screen, _("Rescue"), _("Your system had dirty file systems which you chose not " "to mount. Press return to get a shell from which " "you can fsck and mount your partitions. %s") % msg, [_("OK")], width = 50) - rootmounted = 0 + rootmounted = False else: - if anaconda.ksdata: + if flags.automatedInstall: log.info("System has been mounted under: %s" % ROOT_PATH) else: - ButtonChoiceWindow(anaconda.intf.screen, _("Rescue"), + ButtonChoiceWindow(intf.screen, _("Rescue"), _("Your system has been mounted under %(rootPath)s.\n\n" "Press <return> to get a shell. If you would like to " "make your system the root environment, run the command:\n\n" @@ -371,12 +332,12 @@ def doRescue(anaconda): {'rootPath': ROOT_PATH, 'msg': msg}, [_("OK")] ) - rootmounted = 1 + rootmounted = True # now turn on swap if not readOnly: try: - anaconda.storage.turnOnSwap() + sto.turnOnSwap() except StorageError: log.error("Error enabling swap") @@ -424,7 +385,7 @@ def doRescue(anaconda): raise except Exception as e: log.error("doRescue caught exception: %s" % e) - if anaconda.ksdata: + if flags.automatedInstall: log.error("An error occurred trying to mount some or all of your system") else: if not flags.imageInstall: @@ -434,16 +395,15 @@ def doRescue(anaconda): msg = _("Run %s to unmount the system " "when you are finished.") % ANACONDA_CLEANUP - ButtonChoiceWindow(anaconda.intf.screen, _("Rescue"), + ButtonChoiceWindow(intf.screen, _("Rescue"), _("An error occurred trying to mount some or all of your " "system. Some of it may be mounted under %s.\n\n" "Press <return> to get a shell.") % ROOT_PATH + msg, [_("OK")] ) else: - if anaconda.ksdata and \ - anaconda.ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + if flags.automatedInstall and ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: log.info("No Linux partitions found") - anaconda.intf.screen.finish() + intf.screen.finish() print(_("You don't have any Linux partitions. Rebooting.\n")) sys.exit(0) else: @@ -452,7 +412,7 @@ def doRescue(anaconda): "from the shell.") else: msg = "" - ButtonChoiceWindow(anaconda.intf.screen, _("Rescue Mode"), + ButtonChoiceWindow(intf.screen, _("Rescue Mode"), _("You don't have any Linux partitions. Press " "return to get a shell.%s") % msg, [ _("OK") ], width = 50) @@ -460,28 +420,27 @@ def doRescue(anaconda): msgStr = "" if rootmounted and not readOnly: - anaconda.storage.makeMtab() + sto.makeMtab() try: makeResolvConf(ROOT_PATH) except (OSError, IOError) as e: log.error("error making a resolv.conf: %s" %(e,)) msgStr = _("Your system is mounted under the %s directory.") % (ROOT_PATH,) - ButtonChoiceWindow(anaconda.intf.screen, _("Rescue"), msgStr, [_("OK")] ) + ButtonChoiceWindow(intf.screen, _("Rescue"), msgStr, [_("OK")] ) # we do not need ncurses anymore, shut them down - anaconda.intf.shutdown() + intf.shutdown() #create /etc/fstab in ramdisk, so it is easier to work with RO mounted filesystems makeFStab() # run %post if we've mounted everything - if rootmounted and not readOnly and anaconda.ksdata: + if rootmounted and not readOnly and flags.automatedInstall: from kickstart import runPostScripts - runPostScripts(anaconda.ksdata.scripts) + runPostScripts(ksdata.scripts) # start shell if reboot wasn't requested - if not anaconda.ksdata or \ - not anaconda.ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + if not flags.automatedInstall or not ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: runShell(msg=msgStr) sys.exit(0) diff --git a/pyanaconda/security.py b/pyanaconda/security.py index 50d92e298..2e9532373 100644 --- a/pyanaconda/security.py +++ b/pyanaconda/security.py @@ -19,7 +19,7 @@ # Author(s): Jeremy Katz <katzj@redhat.com> # -import iutil, shlex +import iutil from flags import flags from pyanaconda.constants import ROOT_PATH from pykickstart.constants import * @@ -33,8 +33,6 @@ selinux_states = { SELINUX_DISABLED: "disabled", class Security: def __init__(self): - self.auth = "--enableshadow --passalgo=sha512" - if flags.selinux == 1: self.selinux = SELINUX_ENFORCING else: @@ -50,14 +48,6 @@ class Security: def getSELinux(self): return self.selinux - def _addFingerprint(self): - import rpm - - iutil.resetRpmDb() - ts = rpm.TransactionSet(ROOT_PATH) - # pylint: disable-msg=E1101 - return ts.dbMatch('provides', 'fprintd-pam').count() - def write(self): args = [] @@ -65,22 +55,9 @@ class Security: log.error("unknown selinux state: %s" %(self.selinux,)) return - args = args + [ "--selinux=%s" %(selinux_states[self.selinux],) ] - try: iutil.execWithRedirect("/usr/sbin/lokkit", args, root=ROOT_PATH, stdout="/dev/null", stderr="/dev/null") except (RuntimeError, OSError) as msg: log.error ("lokkit run failed: %s" %(msg,)) - - args = ["--update", "--nostart"] + shlex.split(self.auth) - if self._addFingerprint(): - args += ["--enablefingerprint"] - - try: - iutil.execWithRedirect("/usr/sbin/authconfig", args, - stdout = "/dev/tty5", stderr = "/dev/tty5", - root=ROOT_PATH) - except RuntimeError as msg: - log.error("Error running %s: %s", args, msg) diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index 88038a17a..914096234 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -97,20 +97,15 @@ def storageInitialize(storage, ksdata, protected): if not storage.disks: raise NoDisksError + # kickstart uses all the disks + if flags.automatedInstall: + if not ksdata.ignoredisk.onlyuse: + ksdata.ignoredisk.onlyuse = [d.name for d in storage.disks \ + if d.name not in ksdata.ignoredisk.ignoredisk] + log.debug("onlyuse is now: %s" % (",".join(ksdata.ignoredisk.onlyuse))) + # dispatch.py helper function def storageComplete(anaconda): - if anaconda.dir == DISPATCH_BACK: - rc = anaconda.intf.messageWindow(_("Installation cannot continue."), - _("The storage configuration you have " - "chosen has already been activated. You " - "can no longer return to the disk editing " - "screen. Would you like to continue with " - "the installation process?"), - type = "yesno") - if rc == 0: - sys.exit(0) - return DISPATCH_FORWARD - devs = anaconda.storage.devicetree.getDevicesByType("luks/dm-crypt") existing_luks = False new_luks = False @@ -157,32 +152,6 @@ def storageComplete(anaconda): if anaconda.ksdata: return - # Prevent users from installing on s390x with (a) no /boot volume, (b) the - # root volume on LVM, and (c) the root volume not restricted to a single - # PV - # NOTE: There is not really a way for users to create a / volume - # restricted to a single PV. The backend support is there, but there are - # no UI hook-ups to drive that functionality, but I do not personally - # care. --dcantrell - if iutil.isS390() and \ - not anaconda.storage.mountpoints.has_key('/boot') and \ - anaconda.storage.mountpoints['/'].type == 'lvmlv' and \ - not anaconda.storage.mountpoints['/'].singlePV: - rc = anaconda.intf.messageWindow(_("Missing /boot Volume"), - _("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."), - type="custom", custom_icon="error", - custom_buttons=[_("Go _back"), - _("_Exit installer")], - default=0) - if rc == 0: - return DISPATCH_BACK - sys.exit(1) - rc = anaconda.intf.messageWindow(_("Confirm"), _("The partitioning options you have selected " "will now be written to disk. Any " @@ -241,8 +210,6 @@ def turnOnFilesystems(storage): storage.write() writeEscrowPackets(storage) else: - from pyanaconda.upgrade import bindMountDevDirectory - if upgrade_migrate: # we should write out a new fstab with the migrated fstype shutil.copyfile("%s/etc/fstab" % ROOT_PATH, @@ -250,7 +217,10 @@ def turnOnFilesystems(storage): storage.fsset.write() # and make sure /dev is mounted so we can read the bootloader - bindMountDevDirectory(ROOT_PATH) + getFormat("bind", + device="/dev", + mountpoint="/dev", + exists=True).mount(chroot=ROOT_PATH) def writeEscrowPackets(storage): escrowDevices = filter(lambda d: d.format.type == "luks" and \ @@ -534,6 +504,10 @@ class Storage(object): for device in root.mounts.values() + root.swaps: used_devices.extend(device.ancestors) + if getattr(device, "isLogical", False): + extended = device.disk.format.extendedPartition.path + used_devices.append(self.devicetree.getDeviceByPath(extended)) + for new in [d for d in self.devicetree.leaves if not d.exists]: if new in self.swaps or getattr(new.format, "mountpoint", None): used_devices.extend(new.ancestors) @@ -1111,13 +1085,6 @@ class Storage(object): None), **kwargs.pop("fmt_args", {})) - if kwargs.has_key("disks"): - parents = kwargs.pop("disks") - if isinstance(parents, Device): - kwargs["parents"] = [parents] - else: - kwargs["parents"] = parents - if kwargs.has_key("name"): name = kwargs.pop("name") else: @@ -1146,7 +1113,7 @@ class Storage(object): def newVG(self, *args, **kwargs): """ Return a new LVMVolumeGroupDevice instance. """ - pvs = kwargs.pop("pvs", []) + pvs = kwargs.pop("parents", []) for pv in pvs: if pv not in self.devices: raise ValueError("pv is not in the device tree") @@ -1170,9 +1137,7 @@ class Storage(object): def newLV(self, *args, **kwargs): """ Return a new LVMLogicalVolumeDevice instance. """ - if kwargs.has_key("vg"): - vg = kwargs.pop("vg") - + vg = kwargs.get("parents", [None])[0] mountpoint = kwargs.pop("mountpoint", None) if kwargs.has_key("fmt_type"): kwargs["format"] = getFormat(kwargs.pop("fmt_type"), @@ -1193,7 +1158,7 @@ class Storage(object): if name in self.names: raise ValueError("name already in use") - return LVMLogicalVolumeDevice(name, vg, *args, **kwargs) + return LVMLogicalVolumeDevice(name, *args, **kwargs) def newBTRFS(self, *args, **kwargs): """ Return a new BTRFSVolumeDevice or BRFSSubVolumeDevice. """ @@ -1470,6 +1435,21 @@ class Storage(object): "megabytes which is usually too small to " "install %s.") % (productName,)) + # Prevent users from installing on s390x with (a) no /boot volume, (b) the + # root volume on LVM, and (c) the root volume not restricted to a single + # PV + # NOTE: There is not really a way for users to create a / volume + # restricted to a single PV. The backend support is there, but there are + # no UI hook-ups to drive that functionality, but I do not personally + # care. --dcantrell + if iutil.isS390() and \ + not self.mountpoints.has_key('/boot') and \ + root.type == 'lvmlv' and not root.singlePV: + errors.append(_("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.")) + # FIXME: put a check here for enough space on the filesystems. maybe? # livecds have to have the rootfs type match up @@ -1867,11 +1847,10 @@ class Storage(object): return 0 -def mountExistingSystem(fsset, rootEnt, +def mountExistingSystem(fsset, rootDevice, allowDirty=None, dirtyCB=None, readOnly=None): """ Mount filesystems specified in rootDevice's /etc/fstab file. """ - rootDevice = rootEnt[0] rootPath = ROOT_PATH if dirtyCB is None: dirtyCB = lambda l: False @@ -2699,8 +2678,9 @@ def findExistingInstallations(devicetree): log.warning("setup of %s failed: %s" % (device.name, e)) continue + options = device.format.options + ",ro" try: - device.format.mount(options="ro", mountpoint=ROOT_PATH) + device.format.mount(options=options, mountpoint=ROOT_PATH) except Exception as e: log.warning("mount of %s as %s failed: %s" % (device.name, device.format.type, @@ -2720,8 +2700,11 @@ def findExistingInstallations(devicetree): name = "%s Linux %s for %s" % (product, version, arch) (mounts, swaps) = parseFSTab(devicetree, chroot=ROOT_PATH) - roots.append(Root(mounts=mounts, swaps=swaps, name=name)) device.teardown() + if not mounts and not swaps: + # empty /etc/fstab. weird, but I've seen it happen. + continue + roots.append(Root(mounts=mounts, swaps=swaps, name=name)) return roots @@ -2782,17 +2765,18 @@ def parseFSTab(devicetree, chroot=None): for line in f.readlines(): # strip off comments (line, pound, comment) = line.partition("#") - fields = line.split(None, 3) + fields = line.split(None, 4) - if len(fields) < 4: + if len(fields) < 5: continue - (devspec, mountpoint, fstype, rest) = fields + (devspec, mountpoint, fstype, options, rest) = fields # find device in the tree device = devicetree.resolveDevice(devspec, cryptTab=cryptTab, - blkidTab=blkidTab) + blkidTab=blkidTab, + options=options) if device is None: continue @@ -2804,8 +2788,3 @@ def parseFSTab(devicetree, chroot=None): return (mounts, swaps) -def doKickstartStorage(storage, ksdata, instclass, checker): - ksdata.clearpart.execute(storage, ksdata, instclass) - ksdata.bootloader.execute(storage, ksdata, instclass) - ksdata.autopart.execute(storage, ksdata, instclass) - checker.run() diff --git a/pyanaconda/storage/deviceaction.py b/pyanaconda/storage/deviceaction.py index b16573ad4..617d8b299 100644 --- a/pyanaconda/storage/deviceaction.py +++ b/pyanaconda/storage/deviceaction.py @@ -288,7 +288,10 @@ class ActionDestroyDevice(DeviceAction): # Make sure libparted does not keep cached info for this device # and returns it when we create a new device with the same name if self.device.partedDevice: - self.device.partedDevice.removeFromCache() + try: + self.device.partedDevice.removeFromCache() + except Exception: + pass def requires(self, action): """ Return True if self requires action. diff --git a/pyanaconda/storage/devicelibs/crypto.py b/pyanaconda/storage/devicelibs/crypto.py index ee573229d..35c813f84 100644 --- a/pyanaconda/storage/devicelibs/crypto.py +++ b/pyanaconda/storage/devicelibs/crypto.py @@ -29,6 +29,8 @@ from ..errors import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) +LUKS_METADATA_SIZE = 2.0 # MB + # Keep the character set size a power of two to make sure all characters are # equally likely GENERATED_PASSPHRASE_CHARSET = ("0123456789" diff --git a/pyanaconda/storage/devicelibs/swap.py b/pyanaconda/storage/devicelibs/swap.py index 274b4d03a..5113787c3 100644 --- a/pyanaconda/storage/devicelibs/swap.py +++ b/pyanaconda/storage/devicelibs/swap.py @@ -31,6 +31,8 @@ from . import dm import gettext _ = lambda x: gettext.ldgettext("anaconda", x) +import logging +log = logging.getLogger("anaconda") def mkswap(device, label=''): # We use -f to force since mkswap tends to refuse creation on lvs with @@ -122,3 +124,42 @@ def swapstatus(device): return status +def swapSuggestion(quiet=False, hibernation=False): + """ + Suggest the size of the swap partition that will be created. + + @param quiet: log size information + @param hibernation: calculate swap size big enough for hibernation + @return: calculated swap size + + """ + + mem = iutil.memInstalled()/1024 + mem = ((mem/16)+1)*16 + if not quiet: + log.info("Detected %sM of memory", mem) + + #chart suggested in the discussion with other teams + if mem < 2048: + swap = 2 * mem + + elif 2048 <= mem < 8192: + swap = mem + + elif 8192 <= mem < 65536: + swap = mem / 2 + + else: + swap = 4096 + + if hibernation: + if mem <= 65536: + swap = mem + swap + else: + log.info("Ignoring --hibernation option on systems with 64 GB of RAM or more") + + if not quiet: + log.info("Swap attempt of %sM", swap) + + return swap + diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py index 6b979e850..8fef3d168 100644 --- a/pyanaconda/storage/devices.py +++ b/pyanaconda/storage/devices.py @@ -105,6 +105,7 @@ from devicelibs import lvm from devicelibs import dm from devicelibs import loop from devicelibs import btrfs +from devicelibs import crypto import parted import _ped import block @@ -1934,8 +1935,7 @@ class LUKSDevice(DMCryptDevice): @property def size(self): if not self.exists or not self.partedDevice: - # the LUKS metadata area is 2MB - size = float(self.slave.size) - 2.0 + size = float(self.slave.size) - crypto.LUKS_METADATA_SIZE else: size = self.partedDevice.getSize() return size @@ -1964,7 +1964,7 @@ class LVMVolumeGroupDevice(DMDevice): _type = "lvmvg" _packages = ["lvm2"] - def __init__(self, name, parents, size=None, free=None, + def __init__(self, name, parents=None, size=None, free=None, peSize=None, peCount=None, peFree=None, pvCount=None, uuid=None, exists=False, sysfsPath=''): """ Create a LVMVolumeGroupDevice instance. @@ -2387,7 +2387,7 @@ class LVMLogicalVolumeDevice(DMDevice): _resizable = True _packages = ["lvm2"] - def __init__(self, name, vgdev, size=None, uuid=None, + def __init__(self, name, parents=None, size=None, uuid=None, stripes=1, logSize=0, snapshotSpace=0, format=None, exists=False, sysfsPath='', grow=None, maxsize=None, percent=None, @@ -2418,15 +2418,15 @@ class LVMLogicalVolumeDevice(DMDevice): percent -- percent of VG space to take """ - if isinstance(vgdev, list): - if len(vgdev) != 1: + if isinstance(parents, list): + if len(parents) != 1: raise ValueError("constructor requires a single LVMVolumeGroupDevice instance") - elif not isinstance(vgdev[0], LVMVolumeGroupDevice): + elif not isinstance(parents[0], LVMVolumeGroupDevice): raise ValueError("constructor requires a LVMVolumeGroupDevice instance") - elif not isinstance(vgdev, LVMVolumeGroupDevice): + elif not isinstance(parents, LVMVolumeGroupDevice): raise ValueError("constructor requires a LVMVolumeGroupDevice instance") DMDevice.__init__(self, name, size=size, format=format, - sysfsPath=sysfsPath, parents=vgdev, + sysfsPath=sysfsPath, parents=parents, exists=exists) self.singlePVerr = ("%(mountpoint)s is restricted to a single " @@ -3642,19 +3642,36 @@ class iScsiDiskDevice(DiskDevice, NetworkStorageDevice): self.ibft = kwargs.pop("ibft") self.nic = kwargs.pop("nic") self.initiator = kwargs.pop("initiator") - DiskDevice.__init__(self, device, **kwargs) - NetworkStorageDevice.__init__(self, host_address=self.node.address, - nic=self.nic) - log.debug("created new iscsi disk %s %s:%d via %s:%s" % (self.node.name, - self.node.address, - self.node.port, - self.node.iface, - self.nic)) + + if self.node is None: + # qla4xxx partial offload + name = kwargs.pop("fw_name") + address = kwargs.pop("fw_address") + port = kwargs.pop("fw_port") + DiskDevice.__init__(self, device, **kwargs) + NetworkStorageDevice.__init__(self, + host_address=address, + nic=self.nic) + log.debug("created new iscsi disk %s %s:%s using fw initiator %s" + % (name, address, port, self.initiator)) + else: + DiskDevice.__init__(self, device, **kwargs) + NetworkStorageDevice.__init__(self, host_address=self.node.address, + nic=self.nic) + log.debug("created new iscsi disk %s %s:%d via %s:%s" % (self.node.name, + self.node.address, + self.node.port, + self.node.iface, + self.nic)) def dracutSetupArgs(self): if self.ibft: return set(["iscsi_firmware"]) + # qla4xxx partial offload + if self.node is None: + return set() + address = self.node.address # surround ipv6 addresses with [] if ":" in address: @@ -3943,19 +3960,31 @@ class BTRFSDevice(StorageDevice): def _temp_dir_prefix(self): return "btrfs-tmp.%s" % self.id - def _do_temp_mount(self): + def _do_temp_mount(self, orig=False): if self.format.status or not self.exists: return tmpdir = tempfile.mkdtemp(prefix=self._temp_dir_prefix) - self.format.mount(mountpoint=tmpdir) + if orig: + fmt = self.originalFormat + else: + fmt = self.format + + fmt.mount(mountpoint=tmpdir) def _undo_temp_mount(self): - if self.format.status: - mountpoint = self.format._mountpoint - if os.path.basename(mountpoint).startswith(self._temp_dir_prefix): - self.format.unmount() - os.rmdir(mountpoint) + if getattr(self.format, "_mountpoint", None): + fmt = self.format + elif getattr(self.originalFormat, "_mountpoint", None): + fmt = self.originalFormat + else: + return + + mountpoint = fmt._mountpoint + + if os.path.basename(mountpoint).startswith(self._temp_dir_prefix): + fmt.unmount() + os.rmdir(mountpoint) @property def path(self): @@ -3990,6 +4019,7 @@ class BTRFSVolumeDevice(BTRFSDevice): label=label, volUUID=self.uuid, device=self.path) + self.originalFormat = self.format label = getattr(self.format, "label", None) if label: @@ -4058,7 +4088,7 @@ class BTRFSVolumeDevice(BTRFSDevice): subvols = [] self.setup(orig=True) try: - self._do_temp_mount() + self._do_temp_mount(orig=True) except FSError as e: log.debug("btrfs temp mount failed: %s" % e) return subvols @@ -4127,10 +4157,10 @@ class BTRFSSubVolumeDevice(BTRFSDevice): def _destroy(self): log_method_call(self, self.name, status=self.status) - self.volume._do_temp_mount() - mountpoint = self.volume.format._mountpoint + self.volume._do_temp_mount(orig=True) + mountpoint = self.volume.originalFormat._mountpoint if not mountpoint: raise RuntimeError("btrfs subvol destroy requires mounted volume") btrfs.delete_subvolume(mountpoint, self.name) - self.volume._removeSubVolume() + self.volume._removeSubVolume(self.name) self.volume._undo_temp_mount() diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py index ae69be2b8..df0d15f81 100644 --- a/pyanaconda/storage/devicetree.py +++ b/pyanaconda/storage/devicetree.py @@ -354,6 +354,7 @@ class DeviceTree(object): # don't include "req%d" partition names if ((newdev.type != "partition" or not newdev.name.startswith("req")) and + newdev.type != "btrfs volume" and newdev.name not in self.names): self.names.append(newdev.name) log.info("added %s %s (id %d) to device tree" % (newdev.type, @@ -598,6 +599,20 @@ class DeviceTree(object): self.addIgnoredDisk(name) return True + # Ignore any readonly disks + if (udev_device_is_disk(info) and not + (udev_device_is_cdrom(info) or + udev_device_is_partition(info) or + udev_device_is_dm_partition(info) or + udev_device_is_dm_lvm(info) or + udev_device_is_dm_crypt(info) or + (udev_device_is_md(info) and not + udev_device_get_md_container(info)))): + if iutil.get_sysfs_attr(info["sysfs_path"], 'ro') == '1': + log.debug("Ignoring read only device %s" % name) + self.addIgnoredDisk(name) + return True + # FIXME: check for virtual devices whose slaves are on the ignore list def addUdevLVDevice(self, info): @@ -845,16 +860,26 @@ class DeviceTree(object): kwargs = { "serial": serial, "vendor": vendor, "bus": bus } if udev_device_is_iscsi(info): diskType = iScsiDiskDevice - node = self.iscsi.getNode( - udev_device_get_iscsi_name(info), - udev_device_get_iscsi_address(info), - udev_device_get_iscsi_port(info), - udev_device_get_iscsi_nic(info)) - kwargs["node"] = node - kwargs["nic"] = self.iscsi.ifaces.get(node.iface, node.iface) - kwargs["ibft"] = node in self.iscsi.ibftNodes - kwargs["initiator"] = self.iscsi.initiator - log.info("%s is an iscsi disk" % name) + initiator = udev_device_get_iscsi_initiator(info) + target = udev_device_get_iscsi_name(info) + address = udev_device_get_iscsi_address(info) + port = udev_device_get_iscsi_port(info) + nic = udev_device_get_iscsi_nic(info) + kwargs["initiator"] = initiator + if initiator == self.iscsi.initiator: + node = self.iscsi.getNode(target, address, port, nic) + kwargs["node"] = node + kwargs["ibft"] = node in self.iscsi.ibftNodes + kwargs["nic"] = self.iscsi.ifaces.get(node.iface, node.iface) + log.info("%s is an iscsi disk" % name) + else: + # qla4xxx partial offload + kwargs["node"] = None + kwargs["ibft"] = False + kwargs["nic"] = "offload:not_accessible_via_iscsiadm" + kwargs["fw_address"] = address + kwargs["fw_port"] = port + kwargs["fw_name"] = name elif udev_device_is_fcoe(info): diskType = FcoeDiskDevice kwargs["nic"] = udev_device_get_fcoe_nic(info) @@ -1529,11 +1554,12 @@ class DeviceTree(object): if vol_path in [sv.name for sv in btrfs_dev.subvolumes]: continue fmt = getFormat("btrfs", device=btrfs_dev.path, exists=True, - mountopts="subvol=%d" % vol_id) + mountopts="subvol=%s" % vol_path) subvol = BTRFSSubVolumeDevice(vol_path, vol_id=vol_id, format=fmt, - parents=[btrfs_dev]) + parents=[btrfs_dev], + exists=True) self._addDevice(subvol) def handleUdevDeviceFormat(self, info, device): @@ -2148,7 +2174,7 @@ class DeviceTree(object): """ Return a list of a device's children. """ return [c for c in self._devices if device in c.parents] - def resolveDevice(self, devspec, blkidTab=None, cryptTab=None): + def resolveDevice(self, devspec, blkidTab=None, cryptTab=None, options=None): # find device in the tree device = None if devspec.startswith("UUID="): @@ -2232,6 +2258,22 @@ class DeviceTree(object): lv = "%s-%s" % (vg_name, lv_name) device = self.getDeviceByName(lv) + # check mount options for btrfs volumes in case it's a subvol + if device and device.type == "btrfs volume" and options: + attr = None + if "subvol=" in options: + attr = "name" + val = iutil.get_option_value("subvol", options) + elif "subvolid=" in options: + attr = "vol_id" + val = iutil.get_option_value("subvolid", options) + + if attr and val: + for subvol in device.subvolumes: + if getattr(subvol, attr, None) == val: + device = subvol + break + if device: log.debug("resolved '%s' to '%s' (%s)" % (devspec, device.name, device.type)) else: diff --git a/pyanaconda/storage/fcoe.py b/pyanaconda/storage/fcoe.py index d263dcf80..fc2ad4017 100644 --- a/pyanaconda/storage/fcoe.py +++ b/pyanaconda/storage/fcoe.py @@ -122,14 +122,16 @@ class fcoe(object): iutil.execWithRedirect("dcbtool", [ "sc", nic, "app:fcoe", "e:1", "a:1", "w:1" ], stdout = "/dev/tty5", stderr="/dev/tty5") - iutil.execWithRedirect("fipvlan", [ nic, "-c", "-s" ], + iutil.execWithRedirect("fipvlan", [ "-c", "-s", "-f", + "'-fcoe'", nic], stdout = "/dev/tty5", stderr="/dev/tty5") else: if auto_vlan: # certain network configrations require the VLAN layer module: iutil.execWithRedirect("modprobe", ["8021q"], stdout = "/dev/tty5", stderr="/dev/tty5") - iutil.execWithRedirect("fipvlan", ['-c', '-s', nic], + iutil.execWithRedirect("fipvlan", ['-c', '-s', '-f', + "'-fcoe'", nic], stdout = "/dev/tty5", stderr="/dev/tty5") else: f = open("/sys/module/libfcoe/parameters/create", "w") diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py index 248dd9c3f..eaf2405d3 100644 --- a/pyanaconda/storage/formats/fs.py +++ b/pyanaconda/storage/formats/fs.py @@ -408,6 +408,7 @@ class FS(DeviceFormat): # the other option is to actually replace this instance with an # instance of the new filesystem type. self._type = self.migrationTarget + self._mountType = self.migrationTarget @property def resizeArgs(self): @@ -1123,6 +1124,10 @@ class BTRFS(FS): # filesystem creation is done in storage.devicelibs.btrfs.create_volume pass + def destroy(self, *args, **kwargs): + # filesystem creation is done in storage.devicelibs.btrfs.delete_volume + pass + def setup(self, *args, **kwargs): log_method_call(self, type=self.mountType, device=self.device, mountpoint=self.mountpoint) diff --git a/pyanaconda/storage/formats/swap.py b/pyanaconda/storage/formats/swap.py index 42458d595..e961afe49 100644 --- a/pyanaconda/storage/formats/swap.py +++ b/pyanaconda/storage/formats/swap.py @@ -45,6 +45,9 @@ class SwapSpace(DeviceFormat): _supported = True # is supported _linuxNative = True # for clearpart + #see rhbz#744129 for details + _maxSize = 128 * 1024 + def __init__(self, *args, **kwargs): """ Create a SwapSpace instance. diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index 74eb68537..e9c06f6d0 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -75,7 +75,8 @@ def _scheduleImplicitPartitions(storage, disks): for disk in disks: if storage.encryptedAutoPart: fmt_type = "luks" - fmt_args = {"escrow_cert": storage.autoPartEscrowCert, + fmt_args = {"passphrase": storage.encryptionPassphrase, + "escrow_cert": storage.autoPartEscrowCert, "add_backup_passphrase": storage.autoPartAddBackupPassphrase} else: if storage.autoPartType == AUTOPART_TYPE_LVM: @@ -86,7 +87,7 @@ def _scheduleImplicitPartitions(storage, disks): part = storage.newPartition(fmt_type=fmt_type, fmt_args=fmt_args, grow=True, - disks=[disk]) + parents=[disk]) storage.createDevice(part) devs.append(part) @@ -156,16 +157,21 @@ def _schedulePartitions(storage, disks): request.fstype = storage.liveImage.format.type if request.encrypted and storage.encryptedAutoPart: - fstype = "luks" + fmt_type = "luks" + fmt_args = {"passphrase": storage.encryptionPassphrase, + "escrow_cert": storage.autoPartEscrowCert, + "add_backup_passphrase": storage.autoPartAddBackupPassphrase} else: - fstype = request.fstype + fmt_type = request.fstype + fmt_args = {} - dev = storage.newPartition(fmt_type=fstype, + dev = storage.newPartition(fmt_type=fmt_type, + fmt_args=fmt_args, size=request.size, grow=request.grow, maxsize=request.maxSize, mountpoint=request.mountpoint, - disks=disks, + parents=disks, weight=request.weight) # schedule the device for creation @@ -193,12 +199,10 @@ def _scheduleVolumes(storage, devs): new_container = storage.newVG new_volume = storage.newLV format_name = "lvmpv" - parent_kw = "pvs" else: new_container = storage.newBTRFS new_volume = storage.newBTRFS format_name = "btrfs" - parent_kw = "parents" if storage.encryptedAutoPart: pvs = [] @@ -213,7 +217,7 @@ def _scheduleVolumes(storage, devs): pvs = devs # create a vg containing all of the autopart pvs - container = new_container(**{parent_kw: pvs}) + container = new_container(parents=pvs) storage.createDevice(container) # @@ -252,7 +256,7 @@ def _scheduleVolumes(storage, devs): kwargs = {"mountpoint": request.mountpoint, "fmt_type": request.fstype} if lv: - kwargs.update({"vg": container, + kwargs.update({"parents": [container], "grow": request.grow, "maxsize": request.maxSize, "size": request.size, @@ -1265,7 +1269,7 @@ class Chunk(object): def sortRequests(self): pass - def growRequests(self): + def growRequests(self, uniform=False): """ Calculate growth amounts for requests in this chunk. """ log.debug("Chunk.growRequests: %r" % self) @@ -1281,17 +1285,22 @@ class Chunk(object): while not self.done and self.pool and last_pool != self.pool: last_pool = self.pool # to keep from getting stuck self.base = new_base + if uniform: + growth = last_pool / self.remaining + 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: continue - # Each request is allocated free units from the pool - # based on the relative _base_ sizes of the remaining - # growable requests. - share = p.base / float(self.base) - growth = int(share * last_pool) # truncate, don't round + if not uniform: + # Each request is allocated free units from the pool + # based on the relative _base_ sizes of the remaining + # growable requests. + share = p.base / float(self.base) + growth = int(share * last_pool) # truncate, don't round + p.growth += growth self.pool -= growth log.debug("adding %d (%dMB) to %d (%s)" % diff --git a/pyanaconda/storage/udev.py b/pyanaconda/storage/udev.py index 09e9c25dd..855ee70a3 100644 --- a/pyanaconda/storage/udev.py +++ b/pyanaconda/storage/udev.py @@ -556,19 +556,48 @@ def udev_device_get_iscsi_port(info): # IPV6 contains : within the address, the part after the last : is the port return path_components[address_field].split(":")[-1] -def udev_device_get_iscsi_nic(info): +def udev_device_get_iscsi_session(info): # '/devices/pci0000:00/0000:00:02.0/0000:09:00.0/0000:0a:01.0/0000:0e:00.2/host3/session1/target3:0:0/3:0:0:0/block/sda' # The position of sessionX part depends on device # (e.g. offload vs. sw; also varies for different offload devs) + session = None match = re.match('/.*/(session\d+)', info["sysfs_path"]) if match: session = match.groups()[0] + else: + log.error("udev_device_get_iscsi_session: session not found in %s" % info) + return session + + +def udev_device_get_iscsi_nic(info): + iface = None + session = udev_device_get_iscsi_session(info) + if session: iface = open("/sys/class/iscsi_session/%s/ifacename" % session).read().strip() - else: - iface = None return iface +def udev_device_get_iscsi_initiator(info): + initiator = None + if udev_device_is_partoff_iscsi(info): + host = re.match('.*/(host\d+)', info["sysfs_path"]).groups()[0] + if host: + initiator_file = "/sys/class/iscsi_host/%s/initiatorname" % host + if os.access(initiator_file, os.R_OK): + initiator = open(initiator_file).read().strip() + log.debug("found offload iscsi initiatorname %s in file %s" % + (initiator, initiator_file)) + if initiator.lstrip("(").rstrip(")").lower() == "null": + initiator = None + if initiator is None: + session = udev_device_get_iscsi_session(info) + if session: + initiator = open("/sys/class/iscsi_session/%s/initiatorname" % + session).read().strip() + log.debug("found iscsi initiatorname %s" % initiator) + return initiator + + # fcoe disks have ID_PATH in the form of: # For FCoE directly over the NIC (so no VLAN and thus no DCB): # pci-eth#-fc-${id} diff --git a/pyanaconda/text.py b/pyanaconda/text.py index 1b201c1a2..c924ae338 100644 --- a/pyanaconda/text.py +++ b/pyanaconda/text.py @@ -36,7 +36,7 @@ from localeinfo import expandLangs from flags import flags from textw.constants_text import * from constants import * -from network import hasActiveNetDev +from network import hasActiveNetDev, getDevices from installinterfacebase import InstallInterfaceBase import imp import textw @@ -241,7 +241,7 @@ class PassphraseEntryWindow: res = buttons.buttonPressed(rc) passphrase = None - if res == TEXT_OK_CHECK: + if res == TEXT_OK_CHECK or rc == "F12": passphrase = passphraseentry.value().strip() self.rc = passphrase @@ -389,7 +389,7 @@ class InstallInterface(InstallInterfaceBase): return passphrase def enableNetwork(self): - if len(self.anaconda.network.netdevices) == 0: + if len(getDevices) == 0: return False from textw.netconfig_text import NetworkConfiguratorText w = NetworkConfiguratorText(self.screen, self.anaconda) diff --git a/pyanaconda/textw/add_drive_text.py b/pyanaconda/textw/add_drive_text.py index b28af4fa1..768681437 100644 --- a/pyanaconda/textw/add_drive_text.py +++ b/pyanaconda/textw/add_drive_text.py @@ -24,6 +24,7 @@ from snack import * from constants_text import * from pyanaconda.constants import * import pyanaconda.partIntfHelpers as pih +from pyanaconda import isys import gettext _ = lambda x: gettext.ldgettext("anaconda", x) @@ -172,7 +173,7 @@ class iSCSITextWizard(pih.iSCSIWizard): result = grid.run() button = grid.buttons.buttonPressed(result) self.screen.popWindow() - return True if button == TEXT_OK_CHECK else False + return bool(button == TEXT_OK_CHECK or result == "F12") def destroy_dialogs(self): pass @@ -363,8 +364,7 @@ class addDriveDialog(object): return INSTALL_OK def addFcoeDriveDialog(self, screen): - netdevs = self.anaconda.network.netdevices - devs = netdevs.keys() + devs = network.getDevices() devs.sort() if not devs: @@ -381,7 +381,7 @@ class addDriveDialog(object): interfaceList = Listbox(height=len(devs), scroll=1) for dev in devs: - hwaddr = netdevs[dev].get("HWADDR") + hwaddr = isys.getMacAddress(dev) if hwaddr: desc = "%s - %.50s" % (dev, hwaddr) else: diff --git a/pyanaconda/textw/netconfig_text.py b/pyanaconda/textw/netconfig_text.py index aabd7d580..47db65253 100644 --- a/pyanaconda/textw/netconfig_text.py +++ b/pyanaconda/textw/netconfig_text.py @@ -32,20 +32,19 @@ _ = lambda x: gettext.ldgettext("anaconda", x) class NetworkConfiguratorText: def _handleIPError(self, field, errmsg): - self.anaconda.intf.messageWindow(_("Error With Data"), + self.intf.messageWindow(_("Error With Data"), _("An error occurred converting the " "value entered for " "\"%(field)s\":\n%(errmsg)s") \ % {'field': field, 'errmsg': errmsg}) def _handleIPMissing(self, field): - self.anaconda.intf.messageWindow(_("Error With Data"), + self.intf.messageWindow(_("Error With Data"), _("A value is required for the field %s") % field) - def __init__(self, screen, anaconda): + def __init__(self, screen, intf): self.screen = screen - self.anaconda = anaconda - self.netdevs = self.anaconda.network.netdevices + self.netdevs = network.getDevices() self._initValues() @@ -68,16 +67,13 @@ class NetworkConfiguratorText: dev_list = [] selected_devname = None - devnames = self.netdevs.keys() - devnames.sort() + devnames = self.netdevs.sort() # Preselect device set in kickstart - ksdevice = self.anaconda.network.getKSDevice() - if ksdevice: - ksdevice = ksdevice.iface + ksdevice = network.get_ksdevice_name() for devname in devnames: - hwaddr = self.netdevs[devname].get("HWADDR") + hwaddr = isys.getMacAddress(devname) if hwaddr: desc = "%s - %.50s" % (devname, hwaddr) @@ -267,7 +263,7 @@ class NetworkConfiguratorText: def _checkValues(self): if not self.ipv4Selected and not self.ipv6Selected: - self.anaconda.intf.messageWindow(_("Missing protocol"), + self.intf.messageWindow(_("Missing protocol"), _("You must select at least one protocol version")) return False @@ -350,7 +346,9 @@ class NetworkConfiguratorText: Returns True in case of success, False if failed. """ - dev = self.netdevs[devname] + dev = network.NetworkDevice(ROOT_PATH, devname) + dev.loadIfcfgFile() + nameservers = '' if self.ipv4Selected: @@ -390,22 +388,25 @@ class NetworkConfiguratorText: else: dev.set(("IPV6INIT", "no")) - self.anaconda.network.unsetDNS(devname) + self.netdevs[devname].unsetDNS() if nameservers: - self.anaconda.network.setDNS(nameservers, devname) + self.netdevs[devname].setDNS(nameservers) dev.set(('ONBOOT', 'yes')) - w = self.anaconda.intf.waitWindow(_("Configuring Network Interfaces"), _("Waiting for NetworkManager")) - result = self.anaconda.network.bringUp() + w = self.intf.waitWindow(_("Configuring Network Interfaces"), _("Waiting for NetworkManager")) + dev.writeIfcfgFile() + result = network.waitForConnection() w.pop() if not result: - self.anaconda.intf.messageWindow(_("Network Error"), + self.intf.messageWindow(_("Network Error"), _("There was an error configuring " "network device %s") % dev.iface) dev.set(("ONBOOT", "no")) + dev.writeIfcfgFile() return False + network.resetResolver() return True def _ipv4MethodToggled(self, *args): diff --git a/pyanaconda/textw/network_text.py b/pyanaconda/textw/network_text.py index 58eb81e73..a33d7f544 100644 --- a/pyanaconda/textw/network_text.py +++ b/pyanaconda/textw/network_text.py @@ -26,8 +26,7 @@ from pyanaconda import network class HostnameWindow: def __call__(self, screen, anaconda): - hname = network.getDefaultHostname(anaconda) - anaconda.network.hostname = hname + hname = network.getHostname() return INSTALL_OK # vim:tw=78:ts=4:et:sw=4 diff --git a/pyanaconda/ui/__init__.py b/pyanaconda/ui/__init__.py index 91252c1b5..7e9e31b27 100644 --- a/pyanaconda/ui/__init__.py +++ b/pyanaconda/ui/__init__.py @@ -76,9 +76,9 @@ class UserInterface(object): ### MESSAGE HANDLING METHODS ### def showError(self, message): - """Display an error dialog with the given message. After this dialog - is displayed, anaconda will quit. There is no return value. This - method must be implemented by all UserInterface subclasses. + """Display an error dialog with the given message. There is no return + value. This method must be implemented by all UserInterface + subclasses. In the code, this method should be used sparingly and only for critical errors that anaconda cannot figure out how to recover from. @@ -111,3 +111,16 @@ class UserInterface(object): key=lambda obj: obj.priority)) return actionClasses + + def mainExceptionWindow(self, text, exn_file): + """Return window with the exception and buttons for debugging, bug + reporting and exitting the installer. + + This method will be called only when unhandled exception appears. + """ + raise NotImplementedError + + def saveExceptionWindow(self, account_manager, signature): + """Show a window that provides a way to report a bug.""" + raise NotImplementedError + diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py index b8944c162..0b8489c3e 100644 --- a/pyanaconda/ui/common.py +++ b/pyanaconda/ui/common.py @@ -146,6 +146,7 @@ class Spoke(UIObject): self.storage = storage self.payload = payload self.instclass = instclass + self.applyOnSkip = False def apply(self): """Apply the selections made on this Spoke to the object's preset diff --git a/pyanaconda/ui/gui/Makefile.am b/pyanaconda/ui/gui/Makefile.am index 7a3958cc3..51d43a8da 100644 --- a/pyanaconda/ui/gui/Makefile.am +++ b/pyanaconda/ui/gui/Makefile.am @@ -24,4 +24,4 @@ guidir = $(pkgpyexecdir)/ui/gui gui_PYTHON = *.py uidir = $(datadir)/$(PACKAGE_NAME)/ui/ -dist_ui_DATA = *.ui +dist_ui_DATA = *.glade diff --git a/pyanaconda/ui/gui/TODO b/pyanaconda/ui/gui/TODO index 1eccc6b07..f4170bf24 100644 --- a/pyanaconda/ui/gui/TODO +++ b/pyanaconda/ui/gui/TODO @@ -20,14 +20,14 @@ Fedora 18 s-c-ks into an anaconda wrapper first, though. - Wall of anaconda needs to be taken into account. - Why does the first hub jump left a little bit the first time you use the keyboard to navigate? -- We need a way for users to look at the list of problems resulting from the - software dependency check and the storage sanity check - In custom storage, it'd be nice to be able to say something like "Use this previous installation as the starting storage configuration for the system I'm installing now" - In custom storage, it'd be really nice to be able to drag entries from old roots into the "New Fedora" root as a way of saying "Use this device with the same mountpoint in the new system." +- We're probably going to need to update the auto-generated names of container + devices any time the user updates the hostname. Beyond ====== diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py index dbd0a28b6..5ebb53d15 100644 --- a/pyanaconda/ui/gui/__init__.py +++ b/pyanaconda/ui/gui/__init__.py @@ -19,6 +19,7 @@ # Red Hat Author(s): Chris Lumens <clumens@redhat.com> # import importlib, inspect, os, sys +import meh.ui.gui from pyanaconda.ui import UserInterface, common, collect from pyanaconda.ui.gui.utils import enlightbox @@ -124,13 +125,23 @@ class GraphicalUserInterface(UserInterface): return bool(rc) + def mainExceptionWindow(self, text, exn_file, *args, **kwargs): + meh_intf = meh.ui.gui.GraphicalIntf() + + return meh_intf.mainExceptionWindow(text, exn_file) + + + def saveExceptionWindow(self, account_manager, signature, *args, **kwargs): + meh_intf = meh.ui.gui.GraphicalIntf() + meh_intf.saveExceptionWindow(account_manager, signature) + ### ### SIGNAL HANDLING METHODS ### def _on_continue_clicked(self): - # If we're on the last screen, clicking Continue is the same as clicking Quit. + # If we're on the last screen, clicking Continue quits. if len(self._actions) == 1: - self._on_quit_clicked() + sys.exit(0) return # If the current action wants us to jump to an arbitrary point ahead, @@ -246,6 +257,7 @@ class GUIObject(common.UIObject): self._origStrings = {} self.skipTo = None + self.applyOnSkip = False from gi.repository import Gtk @@ -341,7 +353,7 @@ class GUIObject(common.UIObject): class QuitDialog(GUIObject): builderObjects = ["quitDialog"] mainWidgetName = "quitDialog" - uiFile = "main.ui" + uiFile = "main.glade" def run(self): rc = self.window.run() diff --git a/pyanaconda/ui/gui/hubs/Makefile.am b/pyanaconda/ui/gui/hubs/Makefile.am index d1dad0a50..ae520a6e3 100644 --- a/pyanaconda/ui/gui/hubs/Makefile.am +++ b/pyanaconda/ui/gui/hubs/Makefile.am @@ -22,4 +22,4 @@ hubsdir = $(pkgpyexecdir)/ui/gui/hubs hubs_PYTHON = *.py uidir = $(datadir)/$(PACKAGE_NAME)/ui/hubs -dist_ui_DATA = *.ui +dist_ui_DATA = *.glade diff --git a/pyanaconda/ui/gui/hubs/__init__.py b/pyanaconda/ui/gui/hubs/__init__.py index 8b8ee099a..f5e211f3c 100644 --- a/pyanaconda/ui/gui/hubs/__init__.py +++ b/pyanaconda/ui/gui/hubs/__init__.py @@ -101,8 +101,10 @@ class Hub(GUIObject, common.Hub): # prevent the user from switching away. It's up to the spoke's back # button handler to kill its own layer of main loop. Gtk.main() - action.apply() - action.execute() + + if not action.skipTo or (action.skipTo and action.applyOnSkip): + action.apply() + action.execute() def _createBox(self): from gi.repository import Gtk, AnacondaWidgets diff --git a/pyanaconda/ui/gui/hubs/progress.ui b/pyanaconda/ui/gui/hubs/progress.glade index 1eb32c97e..1eb32c97e 100644 --- a/pyanaconda/ui/gui/hubs/progress.ui +++ b/pyanaconda/ui/gui/hubs/progress.glade diff --git a/pyanaconda/ui/gui/hubs/progress.py b/pyanaconda/ui/gui/hubs/progress.py index 2eb1b75de..b1c1e9314 100644 --- a/pyanaconda/ui/gui/hubs/progress.py +++ b/pyanaconda/ui/gui/hubs/progress.py @@ -31,6 +31,8 @@ import os from pyanaconda.localeinfo import expandLangs from pyanaconda.product import productName +from pyanaconda.flags import flags +from pykickstart.constants import KS_WAIT, KS_SHUTDOWN, KS_REBOOT from pyanaconda.ui.gui.hubs import Hub from pyanaconda.ui.gui.utils import gdk_threaded @@ -40,7 +42,7 @@ __all__ = ["ProgressHub"] class ProgressHub(Hub): builderObjects = ["progressWindow"] mainWidgetName = "progressWindow" - uiFile = "hubs/progress.ui" + uiFile = "hubs/progress.glade" def __init__(self, data, storage, payload, instclass): Hub.__init__(self, data, storage, payload, instclass) @@ -81,6 +83,11 @@ class ProgressHub(Hub): GLib.source_remove(self._rnotes_id) self._progressNotebook.next_page() + + # kickstart install, continue automatically if reboot or shutdown selected + if flags.automatedInstall and self.data.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: + self.continueButton.emit("clicked") + return False q.task_done() @@ -145,7 +152,7 @@ class ProgressHub(Hub): args=(self.storage, self.payload, self.data, self.instclass))) @property - def quitButton(self): + def continueButton(self): return self.builder.get_object("rebootButton") def _init_progress_bar(self, steps): diff --git a/pyanaconda/ui/gui/hubs/summary.ui b/pyanaconda/ui/gui/hubs/summary.glade index 25aa16564..25aa16564 100644 --- a/pyanaconda/ui/gui/hubs/summary.ui +++ b/pyanaconda/ui/gui/hubs/summary.glade diff --git a/pyanaconda/ui/gui/hubs/summary.py b/pyanaconda/ui/gui/hubs/summary.py index 4973ebe87..c867e496a 100644 --- a/pyanaconda/ui/gui/hubs/summary.py +++ b/pyanaconda/ui/gui/hubs/summary.py @@ -26,7 +26,7 @@ __all__ = ["SummaryHub"] class SummaryHub(Hub): builderObjects = ["summaryWindow"] mainWidgetName = "summaryWindow" - uiFile = "hubs/summary.ui" + uiFile = "hubs/summary.glade" # FIXME: I really hate this. diff --git a/pyanaconda/ui/gui/main.ui b/pyanaconda/ui/gui/main.glade index 2f773a48f..2f773a48f 100644 --- a/pyanaconda/ui/gui/main.ui +++ b/pyanaconda/ui/gui/main.glade diff --git a/pyanaconda/ui/gui/spokes/Makefile.am b/pyanaconda/ui/gui/spokes/Makefile.am index 4968a897d..352f4e4fb 100644 --- a/pyanaconda/ui/gui/spokes/Makefile.am +++ b/pyanaconda/ui/gui/spokes/Makefile.am @@ -24,4 +24,4 @@ spokesdir = $(pkgpyexecdir)/ui/gui/spokes spokes_PYTHON = *.py uidir = $(datadir)/$(PACKAGE_NAME)/ui/spokes -dist_ui_DATA = *.ui +dist_ui_DATA = *.glade diff --git a/pyanaconda/ui/gui/spokes/__init__.py b/pyanaconda/ui/gui/spokes/__init__.py index 192022ffa..61aea5cad 100644 --- a/pyanaconda/ui/gui/spokes/__init__.py +++ b/pyanaconda/ui/gui/spokes/__init__.py @@ -31,6 +31,33 @@ class Spoke(GUIObject, common.Spoke): GUIObject.__init__(self, data) common.Spoke.__init__(self, data, storage, payload, instclass) + def apply(self): + """Apply the selections made on this Spoke to the object's preset + data object. This method must be provided by every subclass. + """ + raise NotImplementedError + + @property + def completed(self): + """Has this spoke been visited and completed? If not, a special warning + icon will be shown on the Hub beside the spoke, and a highlighted + message will be shown at the bottom of the Hub. Installation will not + be allowed to proceed until all spokes are complete. + """ + return False + + def execute(self): + """Cause the data object to take effect on the target system. This will + usually be as simple as calling one or more of the execute methods on + the data object. This method does not need to be provided by all + subclasses. + + This method will be called in two different places: (1) Immediately + after initialize on kickstart installs. (2) Immediately after apply + in all cases. + """ + pass + def initialize(self): GUIObject.initialize(self) diff --git a/pyanaconda/ui/gui/spokes/custom.ui b/pyanaconda/ui/gui/spokes/custom.glade index 5f97efad1..5f97efad1 100644 --- a/pyanaconda/ui/gui/spokes/custom.ui +++ b/pyanaconda/ui/gui/spokes/custom.glade diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py index 5a44d4ae7..c76146a72 100644 --- a/pyanaconda/ui/gui/spokes/custom.py +++ b/pyanaconda/ui/gui/spokes/custom.py @@ -58,7 +58,7 @@ new_install_name = _("New %s %s Installation") % (productName, productVersion) class AddDialog(GUIObject): builderObjects = ["addDialog"] mainWidgetName = "addDialog" - uiFile = "spokes/custom.ui" + uiFile = "spokes/custom.glade" def __init__(self, *args, **kwargs): GUIObject.__init__(self, *args, **kwargs) @@ -88,7 +88,7 @@ class AddDialog(GUIObject): class ConfirmDeleteDialog(GUIObject): builderObjects = ["confirmDeleteDialog"] mainWidgetName = "confirmDeleteDialog" - uiFile = "spokes/custom.ui" + uiFile = "spokes/custom.glade" def on_delete_cancel_clicked(self, button, *args): self.window.destroy() @@ -115,7 +115,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker): "partitionStore", "addImage", "removeImage", "settingsImage"] mainWidgetName = "customStorageWindow" - uiFile = "spokes/custom.ui" + uiFile = "spokes/custom.glade" category = StorageCategory title = N_("MANUAL PARTITIONING") diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.ui b/pyanaconda/ui/gui/spokes/datetime_spoke.glade index cdf1cf5b5..cdf1cf5b5 100644 --- a/pyanaconda/ui/gui/spokes/datetime_spoke.ui +++ b/pyanaconda/ui/gui/spokes/datetime_spoke.glade diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.py b/pyanaconda/ui/gui/spokes/datetime_spoke.py index 944cdfc4b..0e2ca77a6 100644 --- a/pyanaconda/ui/gui/spokes/datetime_spoke.py +++ b/pyanaconda/ui/gui/spokes/datetime_spoke.py @@ -46,7 +46,7 @@ POOL_SERVERS_NOTE = _("Note: pool servers may not be available all the time") class NTPconfigDialog(GUIObject): builderObjects = ["ntpConfigDialog", "addImage", "serversStore"] mainWidgetName = "ntpConfigDialog" - uiFile = "spokes/datetime_spoke.ui" + uiFile = "spokes/datetime_spoke.glade" def __init__(self, *args): GUIObject.__init__(self, *args) @@ -244,7 +244,7 @@ class DatetimeSpoke(NormalSpoke): ] mainWidgetName = "datetimeWindow" - uiFile = "spokes/datetime_spoke.ui" + uiFile = "spokes/datetime_spoke.glade" category = LocalizationCategory diff --git a/pyanaconda/ui/gui/spokes/keyboard.ui b/pyanaconda/ui/gui/spokes/keyboard.glade index 1243e22fb..1243e22fb 100644 --- a/pyanaconda/ui/gui/spokes/keyboard.ui +++ b/pyanaconda/ui/gui/spokes/keyboard.glade diff --git a/pyanaconda/ui/gui/spokes/keyboard.py b/pyanaconda/ui/gui/spokes/keyboard.py index f0084b398..cbf340b91 100644 --- a/pyanaconda/ui/gui/spokes/keyboard.py +++ b/pyanaconda/ui/gui/spokes/keyboard.py @@ -42,7 +42,7 @@ class AddLayoutDialog(GUIObject): builderObjects = ["addLayoutDialog", "newLayoutStore", "newLayoutStoreFilter", "newLayoutStoreSort"] mainWidgetName = "addLayoutDialog" - uiFile = "spokes/keyboard.ui" + uiFile = "spokes/keyboard.glade" def __init__(self, *args): GUIObject.__init__(self, *args) @@ -140,7 +140,7 @@ class KeyboardSpoke(NormalSpoke): builderObjects = ["addedLayoutStore", "keyboardWindow", "addImage", "removeImage", "upImage", "downImage", "previewImage"] mainWidgetName = "keyboardWindow" - uiFile = "spokes/keyboard.ui" + uiFile = "spokes/keyboard.glade" category = LocalizationCategory diff --git a/pyanaconda/ui/gui/spokes/lib/Makefile.am b/pyanaconda/ui/gui/spokes/lib/Makefile.am index edf1be594..ecd597cbc 100644 --- a/pyanaconda/ui/gui/spokes/lib/Makefile.am +++ b/pyanaconda/ui/gui/spokes/lib/Makefile.am @@ -22,4 +22,4 @@ spokesdir = $(pkgpyexecdir)/ui/gui/spokes/lib spokes_PYTHON = *.py uidir = $(datadir)/$(PACKAGE_NAME)/ui/spokes/lib -dist_ui_DATA = *.ui +dist_ui_DATA = *.glade diff --git a/pyanaconda/ui/gui/spokes/lib/cart.ui b/pyanaconda/ui/gui/spokes/lib/cart.glade index 92fdfe53a..92fdfe53a 100644 --- a/pyanaconda/ui/gui/spokes/lib/cart.ui +++ b/pyanaconda/ui/gui/spokes/lib/cart.glade diff --git a/pyanaconda/ui/gui/spokes/lib/cart.py b/pyanaconda/ui/gui/spokes/lib/cart.py index dbbfc1d04..3e77a36f0 100644 --- a/pyanaconda/ui/gui/spokes/lib/cart.py +++ b/pyanaconda/ui/gui/spokes/lib/cart.py @@ -42,7 +42,7 @@ def size_str(mb): class SelectedDisksDialog(GUIObject): builderObjects = ["selected_disks_dialog", "disk_store"] mainWidgetName = "selected_disks_dialog" - uiFile = "spokes/lib/cart.ui" + uiFile = "spokes/lib/cart.glade" def initialize(self, disks, free, showRemove=True): for disk in disks: diff --git a/pyanaconda/ui/gui/spokes/lib/detailederror.glade b/pyanaconda/ui/gui/spokes/lib/detailederror.glade new file mode 100644 index 000000000..db9daf262 --- /dev/null +++ b/pyanaconda/ui/gui/spokes/lib/detailederror.glade @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkTextBuffer" id="detailedTextBuffer"/> + <object class="GtkDialog" id="detailedErrorDialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="modal">True</property> + <property name="window_position">center</property> + <property name="type_hint">dialog</property> + <property name="decorated">False</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox2"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">18</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">The following software marked for installation has errors. This is likely caused by an error with +your installation source. You can attempt to remove these packages from your installation, +change your installation source, or quit the installer.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTextView" id="detailedTextView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="cursor_visible">False</property> + <property name="buffer">detailedTextBuffer</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area2"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="detailedCancelButton"> + <property name="label">gtk-cancel</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="use_action_appearance">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + <property name="secondary">True</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">detailedCancelButton</action-widget> + </action-widgets> + </object> +</interface> diff --git a/pyanaconda/ui/gui/spokes/lib/detailederror.py b/pyanaconda/ui/gui/spokes/lib/detailederror.py new file mode 100644 index 000000000..997d58502 --- /dev/null +++ b/pyanaconda/ui/gui/spokes/lib/detailederror.py @@ -0,0 +1,58 @@ +# Detailed error dialog class +# +# Copyright (C) 2011-2012 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Chris Lumens <clumens@redhat.com> +# + +from gi.repository import Gtk + +from pyanaconda.ui.gui import UIObject + +__all__ = ["DetailedErrorDialog"] + +class DetailedErrorDialog(UIObject): + """This class provides a lightboxable dialog to display a very detailed + set of error messages, like might be required to display the results + of package dependency solving or storage sanity checking. + + By default, this dialog has only a single button - cancel, displayed + on the far left hand side of the dialog, with a response ID of 0. + For all other buttons, provide a kwarg named "buttons" as a list of + translated labels. Each will have an incrementing response ID + starting with 1. It is up to the caller of the "run" method to do + something with the returned response ID. + """ + builderObjects = ["detailedErrorDialog", "detailedTextBuffer"] + mainWidgetName = "detailedErrorDialog" + uiFile = "spokes/lib/detailederror.glade" + + def __init__(self, *args, **kwargs): + buttons = kwargs.pop("buttons", []) + UIObject.__init__(self, *args, **kwargs) + + i = 1 + for button in buttons: + self.window.add_button(button, i) + i += 1 + + def refresh(self, msg): + buf = self.builder.get_object("detailedTextBuffer") + buf.set_text(msg, -1) + + def run(self): + return self.window.run() diff --git a/pyanaconda/ui/gui/spokes/network.ui b/pyanaconda/ui/gui/spokes/network.glade index 49c75c289..49c75c289 100644 --- a/pyanaconda/ui/gui/spokes/network.ui +++ b/pyanaconda/ui/gui/spokes/network.glade diff --git a/pyanaconda/ui/gui/spokes/network.py b/pyanaconda/ui/gui/spokes/network.py index fb68c3fc3..9142ad0e1 100644 --- a/pyanaconda/ui/gui/spokes/network.py +++ b/pyanaconda/ui/gui/spokes/network.py @@ -902,7 +902,7 @@ class NetworkControlBox(): class NetworkSpoke(NormalSpoke): builderObjects = ["networkWindow", "liststore_wireless_network", "liststore_devices"] mainWidgetName = "networkWindow" - uiFile = "spokes/network.ui" + uiFile = "spokes/network.glade" title = N_("NETWORK CONFIGURATION") icon = "network-transmit-receive-symbolic" @@ -962,7 +962,7 @@ class NetworkSpoke(NormalSpoke): class NetworkStandaloneSpoke(StandaloneSpoke): builderObjects = ["networkStandaloneWindow", "networkControlBox_vbox", "liststore_wireless_network", "liststore_devices"] mainWidgetName = "networkStandaloneWindow" - uiFile = "spokes/network.ui" + uiFile = "spokes/network.glade" preForHub = SummaryHub priority = 10 @@ -1040,7 +1040,7 @@ if __name__ == "__main__": builder = Gtk.Builder() import os - ui_file_path = os.environ.get('UIPATH')+'spokes/network.ui' + ui_file_path = os.environ.get('UIPATH')+'spokes/network.glade' builder.add_from_file(ui_file_path) n = NetworkControlBox(builder) diff --git a/pyanaconda/ui/gui/spokes/software.ui b/pyanaconda/ui/gui/spokes/software.glade index 20bbaa32e..d0d91325e 100644 --- a/pyanaconda/ui/gui/spokes/software.ui +++ b/pyanaconda/ui/gui/spokes/software.glade @@ -28,6 +28,7 @@ <property name="startup_id">filler</property> <property name="window_name">SOFTWARE SELECTION</property> <signal name="back-clicked" handler="on_back_clicked" swapped="no"/> + <signal name="info-bar-clicked" handler="on_info_bar_clicked" swapped="no"/> <child internal-child="main_box"> <object class="GtkBox" id="AnacondaSpokeWindow-main_box1"> <property name="can_focus">False</property> @@ -181,6 +182,9 @@ <property name="headers_visible">False</property> <property name="headers_clickable">False</property> <property name="search_column">0</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection2"/> + </child> <child> <object class="GtkTreeViewColumn" id="addonSelectedCol"> <property name="title" translatable="yes">column</property> diff --git a/pyanaconda/ui/gui/spokes/software.py b/pyanaconda/ui/gui/spokes/software.py index 9e56a4bab..cf047ab2d 100644 --- a/pyanaconda/ui/gui/spokes/software.py +++ b/pyanaconda/ui/gui/spokes/software.py @@ -25,19 +25,21 @@ N_ = lambda x: x from pyanaconda.flags import flags -from pyanaconda.ui.gui import communication +from pyanaconda.ui.gui import UIObject, communication from pyanaconda.ui.gui.spokes import NormalSpoke -from pyanaconda.ui.gui.utils import gdk_threaded +from pyanaconda.ui.gui.spokes.lib.detailederror import DetailedErrorDialog +from pyanaconda.ui.gui.utils import enlightbox, gdk_threaded from pyanaconda.ui.gui.categories.software import SoftwareCategory -from pyanaconda.ui.gui.utils import enlightbox from .source import AdditionalReposDialog +import sys + __all__ = ["SoftwareSelectionSpoke"] class SoftwareSelectionSpoke(NormalSpoke): builderObjects = ["addonStore", "desktopStore", "softwareWindow"] mainWidgetName = "softwareWindow" - uiFile = "spokes/software.ui" + uiFile = "spokes/software.glade" category = SoftwareCategory @@ -46,7 +48,7 @@ class SoftwareSelectionSpoke(NormalSpoke): def __init__(self, *args, **kwargs): NormalSpoke.__init__(self, *args, **kwargs) - self._error = False + self._errorMsgs = None self._tx_id = None self.selectedGroups = [] @@ -89,12 +91,12 @@ class SoftwareSelectionSpoke(NormalSpoke): try: self.payload.checkSoftwareSelection() except DependencyError as e: - self._error = True + self._errorMsgs = "\n".join(sorted(e.message)) communication.send_message(self.__class__.__name__, _("Error checking software dependencies")) self._tx_id = None else: - self._error = False + self._errorMsgs = None self._tx_id = self.payload.txID finally: communication.send_ready(self.__class__.__name__) @@ -105,7 +107,7 @@ class SoftwareSelectionSpoke(NormalSpoke): from pyanaconda.kickstart import packagesSeen processingDone = not threadMgr.get("AnaCheckSoftwareThread") and \ - not self._error and \ + not self._errorMsgs and \ self._tx_id == self.payload.txID if flags.automatedInstall: @@ -130,7 +132,7 @@ class SoftwareSelectionSpoke(NormalSpoke): from pyanaconda.kickstart import packagesSeen from pyanaconda.threads import threadMgr - if self._error: + if self._errorMsgs: return _("Error checking software selection") if threadMgr.get("AnaPayloadMDThread") or self.payload.baseRepo is None: @@ -187,6 +189,8 @@ class SoftwareSelectionSpoke(NormalSpoke): self.apply() def refresh(self): + from gi.repository import Gtk + from pyanaconda.threads import threadMgr NormalSpoke.refresh(self) @@ -229,6 +233,9 @@ class SoftwareSelectionSpoke(NormalSpoke): self.excludedGroups = [g.name for g in self.data.packages.excludedGroupList] + if self._errorMsgs: + self.window.set_info(Gtk.MessageType.WARNING, _("Error checking software dependencies. Click for details.")) + # Returns the row in the store corresponding to what's selected on the # left hand panel, or None if nothing's selected. def _get_selected_desktop(self): @@ -258,3 +265,28 @@ class SoftwareSelectionSpoke(NormalSpoke): with enlightbox(self.window, self._addRepoDialog.window): response = self._addRepoDialog.run() + def on_info_bar_clicked(self, *args): + if not self._errorMsgs: + return + + dialog = DetailedErrorDialog(self.data, buttons=[_("_Quit"), _("_Remove Packages"), + _("_Modify Software Source")]) + with enlightbox(self.window, dialog.window): + dialog.refresh(self._errorMsgs) + rc = dialog.run() + + dialog.window.destroy() + + if rc == 0: + # Close the dialog so the user can change selections. + pass + elif rc == 1: + # Quit. + sys.exit(0) + elif rc == 2: + # TODO: Attempt to remove the affected packages. + pass + elif rc == 3: + # Send the user to the installation source spoke. + self.skipTo = "SourceSpoke" + self.window.emit("back-clicked") diff --git a/pyanaconda/ui/gui/spokes/source.ui b/pyanaconda/ui/gui/spokes/source.glade index 5259431bc..5259431bc 100644 --- a/pyanaconda/ui/gui/spokes/source.ui +++ b/pyanaconda/ui/gui/spokes/source.glade diff --git a/pyanaconda/ui/gui/spokes/source.py b/pyanaconda/ui/gui/spokes/source.py index c8c708dc9..fd40b5cb3 100644 --- a/pyanaconda/ui/gui/spokes/source.py +++ b/pyanaconda/ui/gui/spokes/source.py @@ -48,7 +48,7 @@ METADATA_ERROR_MESSAGE = _("Error downloading package metadata...") class ProxyDialog(GUIObject): builderObjects = ["proxyDialog"] mainWidgetName = "proxyDialog" - uiFile = "spokes/source.ui" + uiFile = "spokes/source.glade" def on_proxy_cancel_clicked(self, *args): self.window.destroy() @@ -129,7 +129,7 @@ class ProxyDialog(GUIObject): class MediaCheckDialog(GUIObject): builderObjects = ["mediaCheckDialog"] mainWidgetName = "mediaCheckDialog" - uiFile = "spokes/source.ui" + uiFile = "spokes/source.glade" def _checkisoEndsCB(self, pid, status): doneButton = self.builder.get_object("doneButton") @@ -196,7 +196,7 @@ class MediaCheckDialog(GUIObject): class IsoChooser(GUIObject): builderObjects = ["isoChooserDialog", "isoFilter"] mainWidgetName = "isoChooserDialog" - uiFile = "spokes/source.ui" + uiFile = "spokes/source.glade" def refresh(self, currentFile=""): GUIObject.refresh(self) @@ -238,7 +238,7 @@ class IsoChooser(GUIObject): class AdditionalReposDialog(GUIObject): builderObjects = ["additionalReposDialog", "peopleRepositories", "peopleRepositoriesFilter"] mainWidgetName = "additionalReposDialog" - uiFile = "spokes/source.ui" + uiFile = "spokes/source.glade" typingTimeout = 1 @@ -409,7 +409,7 @@ class AdditionalReposDialog(GUIObject): class SourceSpoke(NormalSpoke): builderObjects = ["isoChooser", "isoFilter", "partitionStore", "sourceWindow", "dirImage"] mainWidgetName = "sourceWindow" - uiFile = "spokes/source.ui" + uiFile = "spokes/source.glade" category = SoftwareCategory diff --git a/pyanaconda/ui/gui/spokes/storage.ui b/pyanaconda/ui/gui/spokes/storage.glade index 16fa40271..16fa40271 100644 --- a/pyanaconda/ui/gui/spokes/storage.ui +++ b/pyanaconda/ui/gui/spokes/storage.glade diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py index ec26f7615..d8cccd8c0 100644 --- a/pyanaconda/ui/gui/spokes/storage.py +++ b/pyanaconda/ui/gui/spokes/storage.py @@ -47,7 +47,7 @@ from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog from pyanaconda.ui.gui.categories.storage import StorageCategory from pyanaconda.ui.gui.utils import enlightbox, gdk_threaded -from pyanaconda.storage import doKickstartStorage +from pyanaconda.kickstart import doKickstartStorage from pyanaconda.storage.size import Size from pyanaconda.product import productName from pyanaconda.flags import flags @@ -60,6 +60,9 @@ _ = lambda x: gettext.ldgettext("anaconda", x) N_ = lambda x: x P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z) +import logging +log = logging.getLogger("anaconda") + __all__ = ["StorageSpoke"] class FakeDiskLabel(object): @@ -110,7 +113,7 @@ def size_str(mb): class InstallOptions1Dialog(GUIObject): builderObjects = ["options1_dialog"] mainWidgetName = "options1_dialog" - uiFile = "spokes/storage.ui" + uiFile = "spokes/storage.glade" RESPONSE_CANCEL = 0 RESPONSE_CONTINUE = 1 @@ -246,11 +249,15 @@ class StorageChecker(object): (StorageChecker.errors, StorageChecker.warnings) = self.storage.sanityCheck() communication.send_ready(self._mainSpokeClass, justUpdate=True) + for e in StorageChecker.errors: + log.error(e) + for w in StorageChecker.warnings: + log.warn(w) class StorageSpoke(NormalSpoke, StorageChecker): builderObjects = ["storageWindow"] mainWidgetName = "storageWindow" - uiFile = "spokes/storage.ui" + uiFile = "spokes/storage.glade" category = StorageCategory @@ -260,6 +267,8 @@ class StorageSpoke(NormalSpoke, StorageChecker): def __init__(self, *args, **kwargs): NormalSpoke.__init__(self, *args, **kwargs) + self.applyOnSkip = True + self._ready = False self.selected_disks = self.data.ignoredisk.onlyuse[:] @@ -310,14 +319,9 @@ class StorageSpoke(NormalSpoke, StorageChecker): # user may have set up before now. self.storage.config.clearNonExistent = self.data.autopart.autopart - # Pick the first disk to be the destination device for the bootloader. - # This appears to be the minimum amount of configuration required to - # make autopart happy with the bootloader settings. - if not self.data.bootloader.bootDrive: - self.data.bootloader.bootDrive = self.storage.bootloader.disks[0].name - def execute(self): - doKickstartStorage(self.storage, self.data, self.instclass, self) + doKickstartStorage(self.storage, self.data, self.instclass) + self.run() @property def completed(self): diff --git a/pyanaconda/ui/gui/spokes/welcome.ui b/pyanaconda/ui/gui/spokes/welcome.glade index e5bcc0f7d..e5bcc0f7d 100644 --- a/pyanaconda/ui/gui/spokes/welcome.ui +++ b/pyanaconda/ui/gui/spokes/welcome.glade diff --git a/pyanaconda/ui/gui/spokes/welcome.py b/pyanaconda/ui/gui/spokes/welcome.py index 9c899eb4a..dc4c733ce 100644 --- a/pyanaconda/ui/gui/spokes/welcome.py +++ b/pyanaconda/ui/gui/spokes/welcome.py @@ -190,7 +190,7 @@ class LanguageMixIn(object): class WelcomeLanguageSpoke(LanguageMixIn, StandaloneSpoke): mainWidgetName = "welcomeWindow" - uiFile = "spokes/welcome.ui" + uiFile = "spokes/welcome.glade" builderObjects = LanguageMixIn.builderObjects + [mainWidgetName, "betaWarnDialog"] preForHub = SummaryHub @@ -234,7 +234,7 @@ class WelcomeLanguageSpoke(LanguageMixIn, StandaloneSpoke): class LanguageSpoke(LanguageMixIn, NormalSpoke): mainWidgetName = "languageSpokeWindow" - uiFile = "spokes/welcome.ui" + uiFile = "spokes/welcome.glade" builderObjects = LanguageMixIn.builderObjects + [mainWidgetName, WelcomeLanguageSpoke.mainWidgetName] category = LocalizationCategory diff --git a/pyanaconda/ui/gui/tools/run-spoke.py b/pyanaconda/ui/gui/tools/run-spoke.py index afb6b0193..afddbd1ff 100755 --- a/pyanaconda/ui/gui/tools/run-spoke.py +++ b/pyanaconda/ui/gui/tools/run-spoke.py @@ -97,7 +97,6 @@ instclass = DefaultInstall() payload = YumPayload(ksdata) payload.setup(storage) -payload.install_log = sys.stdout spoke = spokeClass(ksdata, storage, payload, instclass) if hasattr(spoke, "register_event_cb"): diff --git a/pyanaconda/upgrade.py b/pyanaconda/upgrade.py deleted file mode 100644 index 6b128559b..000000000 --- a/pyanaconda/upgrade.py +++ /dev/null @@ -1,341 +0,0 @@ -# -# upgrade.py - Existing install probe and upgrade procedure -# -# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc. -# All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# Author(s): Matt Wilson <msw@redhat.com> -# - -import isys -import os -import iutil -import time -import sys -import os.path -import shutil -import string -import selinux -from flags import flags -from constants import * -from product import productName -from storage import findExistingRootDevices -from storage import mountExistingSystem -from storage.formats import getFormat - -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - -import rpm - -import logging -log = logging.getLogger("anaconda") - -def queryUpgradeContinue(anaconda): - if anaconda.dir == DISPATCH_FORWARD: - return - - rc = anaconda.intf.messageWindow(_("Proceed with upgrade?"), - _("The file systems of the Linux installation " - "you have chosen to upgrade have already been " - "mounted. You cannot go back past this point. " - "\n\n") + - _("Would you like to continue with the upgrade?"), - type="custom", custom_icon=["error","error"], - custom_buttons=[_("_Exit installer"), _("_Continue")]) - if rc == 0: - sys.exit(0) - return DISPATCH_FORWARD - -def setUpgradeRoot(anaconda): - anaconda.upgradeRoot = [] - root_device = None - # kickstart can pass device as device name or uuid. No quotes allowed. - if anaconda.ksdata and anaconda.ksdata.upgrade.root_device is not None: - root_device = anaconda.ksdata.upgrade.root_device - for (dev, label) in anaconda.rootParts: - if ((root_device is not None) and - (root_device == dev.name or root_device == "UUID=%s" % dev.format.uuid)): - anaconda.upgradeRoot.insert(0, (dev,label)) - else: - anaconda.upgradeRoot.append((dev,label)) - -def findRootParts(anaconda): - if anaconda.dir == DISPATCH_BACK: - return - if anaconda.rootParts is None: - (anaconda.rootParts, notUpgradable) = findExistingRootDevices(anaconda, - upgradeany=flags.cmdline.has_key("upgradeany")) - - if notUpgradable and not anaconda.rootParts: - oldInstalls = "" - for product, version, arch, name, tests in notUpgradable: - if None in (product, version): - oldInstalls = _("Unknown release on %s -") % (name) - else: - oldInstalls = "%s %s %s on %s -" % (product, version, arch, name) - - if not tests["product"]: - oldInstalls += _(" Product mismatch.") - if not tests["version"]: - oldInstalls += _(" Version mismatch.") - if not tests["arch"]: - oldInstalls += _(" Architecture mismatch.") - oldInstalls += "\n" - rc = anaconda.intf.messageWindow(_("Cannot Upgrade"), - _("Your current installation cannot be upgraded. This " - "is likely due to it being too old. Only the previous two " - "releases may be upgraded. To upgrade older releases " - "you must first upgrade through all intermediate releases.\n\n" - "%s") % oldInstalls, - type="custom", custom_icon=["error","error"], - custom_buttons=[_("_Exit installer"), _("_Continue")]) - if rc == 0: - sys.exit(0) - - setUpgradeRoot(anaconda) - - if anaconda.rootParts is not None and len(anaconda.rootParts) > 0: - anaconda.dispatch.request_steps_gently("findinstall") - else: - anaconda.dispatch.skip_steps("findinstall") - -def bindMountDevDirectory(instPath): - getFormat("bind", - device="/dev", - mountpoint="/dev", - exists=True).mount(chroot=instPath) - -# returns None if no filesystem exist to migrate -def upgradeMigrateFind(anaconda): - migents = anaconda.storage.migratableDevices - if not migents or len(migents) < 1: - anaconda.dispatch.skip_steps("upgrademigratefs") - else: - anaconda.dispatch.request_steps("upgrademigratefs") - -def copyFromSysimage(filename): - """Mirrors filename from the sysimage on the ramdisk.""" - sysfile = os.path.normpath("%s/%s" % (ROOT_PATH, filename)) - if os.access(sysfile, os.R_OK): - try: - # remove our copy if we have one (think liveinstall) - os.remove(filename) - except OSError: - pass - try: - shutil.copyfile(sysfile, filename) - except OSError as e: - log.error("Error copying %s to sysimage: %s" %(sysfile, e.strerror)) - return False - else: - log.error("Error copying %s to sysimage, file not accessible." % sysfile) - return False - return True - -def restoreTime(anaconda): - """Load time setup for upgrade install. - - We need to find out the timezone and the UTC parameter of the old system and - set the system time accordingly, so timestamps are set correctly for the - files the upgrade procedure will create. - - This is pretty much what packages.setupTimezone() does in reverse. - """ - if anaconda.dir == DISPATCH_BACK: - return - if os.environ.has_key("TZ"): - del os.environ["TZ"] - copyFromSysimage('/etc/localtime') - copyFromSysimage('/etc/adjtime') - if iutil.isS390(): - return - args = [ "--hctosys" ] - try: - iutil.execWithRedirect("/sbin/hwclock", args,stdout = "/dev/tty5", - stderr = "/dev/tty5") - except RuntimeError: - log.error("Failed to set the clock.") - -# XXX handle going backwards -def upgradeMountFilesystems(anaconda): - # mount everything and turn on swap - - try: - mountExistingSystem(anaconda.storage.fsset, anaconda.upgradeRoot[0], allowDirty = 0) - except ValueError as e: - log.error("Error mounting filesystem: %s" % e) - anaconda.intf.messageWindow(_("Mount failed"), - _("The following error occurred when mounting the file " - "systems listed in /etc/fstab. Please fix this problem " - "and try to upgrade again.\n%s" % e)) - sys.exit(0) - except IndexError as e: - # The upgrade root is search earlier but we give the message here. - log.debug("No upgrade root was found.") - if anaconda.ksdata and anaconda.ksdata.upgrade.upgrade: - anaconda.intf.messageWindow(_("Upgrade root not found"), - _("The root for the previously installed system was not " - "found."), type="custom", - custom_icon="info", - custom_buttons=[_("Exit installer")]) - sys.exit(0) - else: - rc = anaconda.intf.messageWindow(_("Upgrade root not found"), - _("The root for the previously installed system was not " - "found. You can exit installer or backtrack to choose " - "installation instead of upgrade."), - type="custom", - custom_buttons = [ _("_Back"), - _("_Exit installer") ], - custom_icon="question") - if rc == 0: - return DISPATCH_BACK - elif rc == 1: - sys.exit(0) - - checkLinks = ( '/etc', '/var', '/var/lib', '/var/lib/rpm', - '/boot', '/tmp', '/var/tmp', '/root', - '/bin/sh', '/usr/tmp') - badLinks = [] - for n in checkLinks: - if not os.path.islink(ROOT_PATH + n): continue - l = os.readlink(ROOT_PATH + n) - if l[0] == '/': - badLinks.append(n) - - if badLinks: - message = _("The following files are absolute symbolic " - "links, which we do not support during an " - "upgrade. Please change them to relative " - "symbolic links and restart the upgrade.\n\n") - for n in badLinks: - message = message + '\t' + n + '\n' - anaconda.intf.messageWindow(_("Absolute Symlinks"), message) - sys.exit(0) - - # fix for 80446 - badLinks = [] - mustBeLinks = ( '/usr/tmp', ) - for n in mustBeLinks: - if not os.path.islink(ROOT_PATH + n): - badLinks.append(n) - - if badLinks: - message = _("The following are directories which should instead " - "be symbolic links, which will cause problems with the " - "upgrade. Please return them to their original state " - "as symbolic links and restart the upgrade.\n\n") - for n in badLinks: - message = message + '\t' + n + '\n' - anaconda.intf.messageWindow(_("Invalid Directories"), message) - sys.exit(0) - - anaconda.storage.turnOnSwap(upgrading=True) - anaconda.storage.mkDevRoot() - - # Move /etc/rpm/platform out of the way. - if os.path.exists(ROOT_PATH + "/etc/rpm/platform"): - shutil.move(ROOT_PATH + "/etc/rpm/platform", - ROOT_PATH + "/etc/rpm/platform.rpmsave") - - # if they've been booting with selinux disabled, then we should - # disable it during the install as well (#242510) - try: - if os.path.exists(ROOT_PATH + "/.autorelabel"): - ctx = selinux.getfilecon(ROOT_PATH + "/.autorelabel")[1] - if not ctx or ctx == "unlabeled": - flags.selinux = False - log.info("Disabled SELinux for upgrade based on /.autorelabel") - except Exception as e: - log.warning("error checking selinux state: %s" %(e,)) - -def upgradeUsr(anaconda): - """ - Handle the upgrade of /bin, /sbin, /lib, /lib64 to symlinks into /usr/ - This uses dracut's convertfs module - """ - dirs = ["/bin", "/sbin", "/lib", "/lib64"] - dirs = [ROOT_PATH+d for d in dirs] - if all(map(os.path.islink, dirs)): - log.info("upgradeusr dirs are already symlinks") - return - - if anaconda.intf is not None: - w = anaconda.intf.waitWindow(_("Upgrade /usr symlinks"), - _("Running /usr merge script")) - - if iutil.execWithRedirect("/usr/lib/dracut/modules.d/30convertfs/convertfs.sh", - [ROOT_PATH], - stdout="/dev/tty5", stderr="/dev/tty5"): - log.error("convertfs failed") - - if anaconda.intf is not None: - w.pop() - rc = anaconda.intf.messageWindow(_("/usr merge failed"), - _("The /usr merge script failed. This is required" - " for Fedora 17 to work. The upgrade cannot continue." - "\n\n")) - sys.exit(0) - log.info("convertfs was successful") - - if anaconda.intf is not None: - w.pop() - -def setSteps(anaconda): - dispatch = anaconda.dispatch - dispatch.reset_scheduling() # scrap what is scheduled - # in case we are scheduling steps from the examine GUI, some of them are - # already done: - dispatch.schedule_steps_gently( - "language", - "keyboard", - "filtertype", - "filter", - "storageinit", - "findrootparts", - "findinstall" - ) - # schedule the rest: - dispatch.schedule_steps( - "upgrademount", - "restoretime", - "upgrademigfind", - "upgrademigratefs", - "enablefilesystems", - "upgradecontinue", - "upgradeusr", - "reposetup", - "upgbootloader", - "postselection", - "reipl", - "install", - "preinstallconfig", - "installpackages", - "postinstallconfig", - "instbootloader", - "dopostaction", - "methodcomplete", - "complete" - ) - - if not iutil.isX86() and not iutil.isS390(): - dispatch.skip_steps("bootloader") - - if not iutil.isX86(): - dispatch.skip_steps("upgbootloader") - - dispatch.skip_steps("cleardiskssel") diff --git a/pyanaconda/vnc.py b/pyanaconda/vnc.py index a7ccc7643..b06ac8743 100644 --- a/pyanaconda/vnc.py +++ b/pyanaconda/vnc.py @@ -80,12 +80,11 @@ class VncServer: # see if we can sniff out network info netinfo = network.Network() - devices = netinfo.netdevices active_devs = network.getActiveNetDevs() self.ip = None if active_devs != []: - devname = devices[active_devs[0]].iface + devname = active_devs[0] try: ips = (isys.getIPAddresses(devname, version=4) + isys.getIPAddresses(devname, version=6)) @@ -105,7 +104,7 @@ class VncServer: except Exception as e: log.debug("Exception caught trying to get host name of %s: %s" % (ipstr, e)) - self.name = network.getDefaultHostname(self.anaconda) + self.name = network.getHostname() else: if len(hinfo) == 3: self.name = hinfo[0] @@ -187,7 +186,11 @@ class VncServer: self.log.info(_("Starting VNC...")) # Lets call it from here for now. - self.initialize() + try: + self.initialize() + except Exception, e: + stdoutLog.critical("Could not initialize the VNC server: %s" % e) + sys.exit(1) if self.password and len(self.password) < 6: self.changeVNCPasswdWindow() diff --git a/pyanaconda/yuminstall.py b/pyanaconda/yuminstall.py index 0a3f29229..bb98b860c 100644 --- a/pyanaconda/yuminstall.py +++ b/pyanaconda/yuminstall.py @@ -1623,10 +1623,11 @@ reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anacon if os.access("/etc/modprobe.d/anaconda.conf", os.R_OK): shutil.copyfile("/etc/modprobe.d/anaconda.conf", ROOT_PATH + "/etc/modprobe.d/anaconda.conf") + network.write_sysconfig_network() + network.disableIPV6() + network.copyConfigToPath(ROOT_PATH) if not anaconda.ksdata: - anaconda.instClass.setNetworkOnbootDefault(anaconda.network) - anaconda.network.write() - anaconda.network.copyConfigToPath() + anaconda.instClass.setNetworkOnbootDefault() anaconda.storage.write() else: # ensure that /etc/mtab is a symlink to /proc/self/mounts |