#!/usr/bin/python
#
# raid.py - raid probing control
#
# Erik Troan <ewt@redhat.com>
#
# Copyright 1999-2002 Red Hat, Inc.
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
#
# You should have received a copy of the GNU Library Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
"""Raid probing control."""

import parted
import isys
import os
import partitioning
import partedUtils

from rhpl.log import log

# these arches can have their /boot on RAID and not have their
# boot loader blow up
raidBootArches = [ "i386", "x86_64", "ppc" ]

def scanForRaid(drives):
    """Scans for raid devices on drives.

    drives is a list of device names.
    Returns a list of (mdMinor, devices, level, totalDisks) tuples.
    """
    
    raidSets = {}
    raidDevices = {}

    for d in drives:
        parts = []
	isys.makeDevInode(d, "/tmp/" + d)
        try:
            dev = parted.PedDevice.get("/tmp/" + d)
            disk = parted.PedDisk.new(dev)

            raidParts = partedUtils.get_raid_partitions(disk)
            for part in raidParts:
                parts.append(partedUtils.get_partition_name(part))
        except:
            pass

	os.remove("/tmp/" + d)
        for dev in parts:
            try:
                (major, minor, raidSet, level, nrDisks, totalDisks, mdMinor) =\
                        isys.raidsb(dev)
            except ValueError:
                # bad magic, this can't be part of our raid set
                log("reading raid sb failed for %s",dev)
                continue

	    if raidSets.has_key(raidSet):
	    	(knownLevel, knownDisks, knownMinor, knownDevices) = \
			raidSets[raidSet]
		if knownLevel != level or knownDisks != totalDisks or \
		   knownMinor != mdMinor:
                    # Raise hell
		    log("raid set inconsistency for md%d: "
                        "all drives in this raid set do not "
                        "agree on raid parameters.  Skipping raid device",
                        mdMinor)
                    continue
		knownDevices.append(dev)
		raidSets[raidSet] = (knownLevel, knownDisks, knownMinor,
				     knownDevices)
	    else:
		raidSets[raidSet] = (level, totalDisks, mdMinor, [dev,])

	    if raidDevices.has_key(mdMinor):
	    	if (raidDevices[mdMinor] != raidSet):
		    log("raid set inconsistency for md%d: "
                        "found members of multiple raid sets "
                        "that claim to be md%d.  Using only the first "
                        "array found.", mdMinor, mdMinor)
                    continue
	    else:
	    	raidDevices[mdMinor] = raidSet

    raidList = []
    for key in raidSets.keys():
	(level, totalDisks, mdMinor, devices) = raidSets[key]
	if len(devices) == totalDisks - 1 and level in (1, 5, 6):
            log("missing components of raid device md%d.  The "
                "raid device needs %d drive(s) and only %d (was/were) found. "
                "This raid device will be started in degraded mode.", mdMinor,
                totalDisks, len(devices))
	elif len(devices) == totalDisks - 2 and level == 6:
            log("missing components of raid device md%d.  The "
                "raid device needs %d drive(s) and only %d (was/were) found. "
                "This raid device will be started in degraded mode.", mdMinor,
                totalDisks, len(devices))
        elif len(devices) < totalDisks:
            log("missing components of raid device md%d.  The "
                "raid device needs %d drive(s) and only %d (was/were) found. "
                "This raid device will not be started.", mdMinor,
                totalDisks, len(devices))
	    continue
	raidList.append((mdMinor, devices, level, totalDisks))

    return raidList
		
def startAllRaid(driveList):
    """Do a raid start on raid devices and return a list like scanForRaid."""
    rc = []
    mdList = scanForRaid(driveList)
    for mdDevice, deviceList, level, numActive in mdList:
    	devName = "md%d" % (mdDevice,)
	isys.raidstart(devName, deviceList[0])
        rc.append((devName, deviceList, level, numActive))
    return rc

def stopAllRaid(mdList):
    """Do a raid stop on each of the raid device tuples given."""
    for dev, devices, level, numActive in mdList:
	isys.raidstop(dev)

def isRaid6(raidlevel):
    """Return whether raidlevel is a valid descriptor of RAID6."""
    if raidlevel == "RAID6":
        return 1
    elif raidlevel == 6:
        return 1
    elif raidlevel == "6":
        return 1
    return 0

def isRaid5(raidlevel):
    """Return whether raidlevel is a valid descriptor of RAID5."""
    if raidlevel == "RAID5":
        return 1
    elif raidlevel == 5:
        return 1
    elif raidlevel == "5":
        return 1
    return 0

def isRaid1(raidlevel):
    """Return whether raidlevel is a valid descriptor of RAID1."""
    if raidlevel == "RAID1":
        return 1
    elif raidlevel == 1:
        return 1
    elif raidlevel == "1":
        return 1
    return 0

def isRaid0(raidlevel):
    """Return whether raidlevel is a valid descriptor of RAID0."""
    if raidlevel == "RAID0":
        return 1
    elif raidlevel == 0:
        return 1
    elif raidlevel == "0":
        return 1
    return 0

def get_raid_min_members(raidlevel):
    """Return the minimum number of raid members required for raid level"""
    if isRaid0(raidlevel):
        return 2
    elif isRaid1(raidlevel):
        return 1
    elif isRaid5(raidlevel):
        return 2
    elif isRaid6(raidlevel):
        return 2
    else:
        raise ValueError, "invalid raidlevel in get_raid_min_members"

def get_raid_max_spares(raidlevel, nummembers):
    """Return the maximum number of raid spares for raidlevel."""
    if isRaid0(raidlevel):
        return 0
    elif isRaid1(raidlevel) or isRaid5(raidlevel) or isRaid6(raidlevel):
        return max(0, nummembers - get_raid_min_members(raidlevel))
    else:
        raise ValueError, "invalid raidlevel in get_raid_max_spares"

def register_raid_device(mdname, newdevices, newlevel, newnumActive):
    """Register a new RAID device in the mdlist."""
    for dev, devices, level, numActive in partedUtils.DiskSet.mdList:
        if mdname == dev:
            if (devices != newdevices or level != newlevel or
                numActive != newnumActive):
                raise ValueError, "%s is already in the mdList!" % (mdname,)
            else:
                return
    partedUtils.DiskSet.mdList.append((mdname, newdevices[:], newlevel,
                                       newnumActive))

def lookup_raid_device(mdname):
    """Return the requested RAID device information."""
    for dev, devices, level, numActive in partedUtils.DiskSet.mdList:
        if mdname == dev:
            return (dev, devices, level, numActive)
    raise KeyError, "md device not found"

def getRaidLevels():
    avail = []
    try:
        f = open("/proc/mdstat", "r")
    except:
        pass
    else:
        for l in f.readlines():
            if not l.startswith("Personalities"):
                continue
            for tok in l.split():
                for lev in ("RAID0", "RAID1", "RAID5", "RAID6"):
                    if tok.upper().find(lev) != -1:
                        avail.append(lev)

        f.close()

    return avail


    
