#
# mdraid.py
# mdraid functions
#
# Copyright (C) 2009 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 .
#
# Author(s): Dave Lehman
#
import os
from pyanaconda import iutil
from ..errors import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
import logging
log = logging.getLogger("storage")
# these defaults were determined empirically
MD_SUPERBLOCK_SIZE = 2.0 # MB
MD_CHUNK_SIZE = 0.5 # MB
# raidlevels constants
RAID10 = 10
RAID6 = 6
RAID5 = 5
RAID4 = 4
RAID1 = 1
RAID0 = 0
def getRaidLevels():
mdstat_descriptors = {
RAID10: ("[RAID10]", "[raid10]"),
RAID6: ("[RAID6]", "[raid6]"),
RAID5: ("[RAID5]", "[raid5]"),
RAID4: ("[RAID4]", "[raid4]"),
RAID1: ("[RAID1]", "[raid1]"),
RAID0: ("[RAID0]", "[raid0]"),
}
avail = []
try:
f = open("/proc/mdstat", "r")
except IOError:
pass
else:
for l in f.readlines():
if not l.startswith("Personalities"):
continue
lst = l.split()
for level in mdstat_descriptors:
for d in mdstat_descriptors[level]:
if d in lst:
avail.append(level)
break
f.close()
avail.sort()
return avail
raid_levels = getRaidLevels()
raid_descriptors = {RAID10: ("raid10", "RAID10", "10", 10),
RAID6: ("raid6", "RAID6", "6", 6),
RAID5: ("raid5", "RAID5", "5", 5),
RAID4: ("raid4", "RAID4", "4", 4),
RAID1: ("raid1", "mirror", "RAID1", "1", 1),
RAID0: ("raid0", "stripe", "RAID0", "0", 0)}
def raidLevel(descriptor):
for level in raid_levels:
if isRaid(level, descriptor):
return level
else:
raise MDRaidError("invalid raid level descriptor %s" % descriptor)
def raidLevelString(level):
if level in raid_descriptors.keys():
return raid_descriptors[level][0]
else:
raise MDRaidError("invalid raid level constant %s" % level)
def isRaid(raid, raidlevel):
"""Return whether raidlevel is a valid descriptor of raid"""
if raid in raid_descriptors:
return raidlevel in raid_descriptors[raid]
else:
raise MDRaidError("invalid raid level %d" % raid)
def get_raid_min_members(raidlevel):
"""Return the minimum number of raid members required for raid level"""
raid_min_members = {RAID10: 2,
RAID6: 4,
RAID5: 3,
RAID4: 3,
RAID1: 2,
RAID0: 2}
for raid, min_members in raid_min_members.items():
if isRaid(raid, raidlevel):
return min_members
raise MDRaidError("invalid raid level %d" % raidlevel)
def get_raid_max_spares(raidlevel, nummembers):
"""Return the maximum number of raid spares for raidlevel."""
raid_max_spares = {RAID10: lambda: max(0, nummembers - get_raid_min_members(RAID10)),
RAID6: lambda: max(0, nummembers - get_raid_min_members(RAID6)),
RAID5: lambda: max(0, nummembers - get_raid_min_members(RAID5)),
RAID4: lambda: max(0, nummembers - get_raid_min_members(RAID4)),
RAID1: lambda: max(0, nummembers - get_raid_min_members(RAID1)),
RAID0: lambda: 0}
for raid, max_spares_func in raid_max_spares.items():
if isRaid(raid, raidlevel):
return max_spares_func()
raise MDRaidError("invalid raid level %d" % raidlevel)
def get_member_space(size, disks, level=None):
space = 0 # size of *each* member device
if isinstance(level, str):
level = raidLevel(level)
min_members = get_raid_min_members(level)
if disks < min_members:
raise MDRaidError("raid%d requires at least %d disks"
% (level, min_members))
if level == RAID0:
# you need the sum of the member sizes to equal your desired capacity
space = size / disks
elif level == RAID1:
# you need each member's size to equal your desired capacity
space = size
elif level in (RAID4, RAID5):
# you need the sum of all but one members' sizes to equal your desired
# capacity
space = size / (disks - 1)
elif level == RAID6:
# you need the sum of all but two members' sizes to equal your desired
# capacity
space = size / (disks - 2)
elif level == RAID10:
# you need the sum of the member sizes to equal twice your desired
# capacity
space = size / (disks / 2.0)
space += MD_SUPERBLOCK_SIZE
return space * disks
def mdadm(args):
ret = iutil.execWithRedirect("mdadm", args,
stdout = "/dev/tty5",
stderr = "/dev/tty5")
if ret:
raise MDRaidError("running mdadm " + " ".join(args) + " failed")
def mdcreate(device, level, disks, spares=0, metadataVer=None, bitmap=False):
argv = ["--create", device, "--run", "--level=%s" % level]
raid_devs = len(disks) - spares
argv.append("--raid-devices=%d" % raid_devs)
if spares:
argv.append("--spare-devices=%d" % spares)
if metadataVer:
argv.append("--metadata=%s" % metadataVer)
if bitmap:
argv.append("--bitmap=internal")
argv.extend(disks)
try:
mdadm(argv)
except MDRaidError as msg:
raise MDRaidError("mdcreate failed for %s: %s" % (device, msg))
def mddestroy(device):
args = ["--zero-superblock", device]
try:
mdadm(args)
except MDRaidError as msg:
raise MDRaidError("mddestroy failed for %s: %s" % (device, msg))
def mdadd(device):
args = ["--incremental", "--quiet"]
args.append(device)
try:
mdadm(args)
except MDRaidError as msg:
raise MDRaidError("mdadd failed for %s: %s" % (device, msg))
def mdactivate(device, members=[], super_minor=None, uuid=None):
if super_minor is None and not uuid:
raise MDRaidError("mdactivate requires either a uuid or a super-minor")
if uuid:
identifier = "--uuid=%s" % uuid
else:
identifier = ""
args = ["--assemble", device, identifier, "--run"]
args += members
try:
mdadm(args)
except MDRaidError as msg:
raise MDRaidError("mdactivate failed for %s: %s" % (device, msg))
def mddeactivate(device):
args = ["--stop", device]
try:
mdadm(args)
except MDRaidError as msg:
raise MDRaidError("mddeactivate failed for %s: %s" % (device, msg))
def mdexamine(device):
vars = iutil.execWithCapture("mdadm",
["--examine", "--brief", device],
stderr="/dev/tty5").split()
info = {}
if len(vars) > 1 and vars[1].startswith("/dev/md"):
info["device"] = vars[1]
vars = vars[2:]
elif len(vars) > 1:
vars = vars[1:]
for var in vars:
(name, equals, value) = var.partition("=")
if not equals:
continue
info[name.lower()] = value.strip()
return info
def md_node_from_name(name):
named_path = "/dev/md/" + name
try:
node = os.path.basename(os.readlink(named_path))
except OSError as e:
raise MDRaidError("md_node_from_name failed: %s" % e)
else:
return node
def name_from_md_node(node):
md_dir = "/dev/md"
name = None
# It's sad, but it's what we've got.
for link in os.listdir(md_dir):
full_path = "%s/%s" % (md_dir, link)
md_name = os.path.basename(os.readlink(full_path))
log.debug("link: %s -> %s" % (link, os.readlink(full_path)))
if md_name == node:
name = link
break
if not name:
raise MDRaidError("name_from_md_node(%s) failed" % node)
log.debug("returning %s" % name)
return name