2272 lines
91 KiB
Python
2272 lines
91 KiB
Python
# devicetree.py
|
|
# Device management for anaconda's storage configuration module.
|
|
#
|
|
# 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): Dave Lehman <dlehman@redhat.com>
|
|
#
|
|
|
|
import os
|
|
import stat
|
|
import block
|
|
import re
|
|
|
|
from errors import *
|
|
from devices import *
|
|
from deviceaction import *
|
|
from partitioning import shouldClear
|
|
from pykickstart.constants import *
|
|
import formats
|
|
import devicelibs.mdraid
|
|
import devicelibs.dm
|
|
import devicelibs.lvm
|
|
import devicelibs.mpath
|
|
from udev import *
|
|
from .storage_log import log_method_call
|
|
|
|
import gettext
|
|
_ = lambda x: gettext.ldgettext("anaconda", x)
|
|
|
|
import logging
|
|
log = logging.getLogger("storage")
|
|
|
|
def getLUKSPassphrase(intf, device, globalPassphrase):
|
|
""" Obtain a passphrase for a LUKS encrypted block device.
|
|
|
|
The format's mapping name must already be set and the backing
|
|
device must already be set up before calling this function.
|
|
|
|
If successful, this function leaves the device mapped.
|
|
|
|
Return value is a two-tuple: (passphrase, isglobal)
|
|
|
|
passphrase is the passphrase string, if obtained
|
|
isglobal is a boolean indicating whether the passphrase is global
|
|
|
|
Either or both can be None, depending on the outcome.
|
|
"""
|
|
if device.format.type != "luks":
|
|
# this function only works on luks devices
|
|
raise ValueError("not a luks device")
|
|
|
|
if not device.status:
|
|
# the device should have already been set up
|
|
raise RuntimeError("device is not set up")
|
|
|
|
if device.format.status:
|
|
# the device is already mapped
|
|
raise RuntimeError("device is already mapped")
|
|
|
|
if not device.format.configured and globalPassphrase:
|
|
# try the given passphrase first
|
|
device.format.passphrase = globalPassphrase
|
|
|
|
try:
|
|
device.format.setup()
|
|
except CryptoError as e:
|
|
device.format.passphrase = None
|
|
else:
|
|
# we've opened the device so we're done.
|
|
return (globalPassphrase, False)
|
|
|
|
if not intf:
|
|
return (None, None)
|
|
|
|
buttons = [_("Back"), _("Continue")]
|
|
passphrase_incorrect = False
|
|
while True:
|
|
if passphrase_incorrect:
|
|
# TODO: add a flag to passphraseEntryWindow to say the last
|
|
# passphrase was incorrect so try again
|
|
passphrase_incorrect = False
|
|
(passphrase, isglobal) = intf.passphraseEntryWindow(device.name)
|
|
if not passphrase:
|
|
rc = intf.messageWindow(_("Confirm"),
|
|
_("Are you sure you want to skip "
|
|
"entering a passphrase for device "
|
|
"%s?\n\n"
|
|
"If you skip this step the "
|
|
"device's contents will not "
|
|
"be available during "
|
|
"installation.") % device.name,
|
|
type = "custom",
|
|
default = 0,
|
|
custom_buttons = buttons)
|
|
if rc == 0:
|
|
continue
|
|
else:
|
|
passphrase = None
|
|
isglobal = None
|
|
log.info("skipping passphrase for %s" % (device.name,))
|
|
break
|
|
|
|
device.format.passphrase = passphrase
|
|
|
|
try:
|
|
device.format.setup()
|
|
except CryptoError as e:
|
|
device.format.passphrase = None
|
|
passphrase_incorrect = True
|
|
else:
|
|
# we've opened the device so we're done.
|
|
break
|
|
|
|
return (passphrase, isglobal)
|
|
|
|
|
|
class DeviceTree(object):
|
|
""" A quasi-tree that represents the devices in the system.
|
|
|
|
The tree contains a list of device instances, which does not
|
|
necessarily reflect the actual state of the system's devices.
|
|
DeviceActions are used to perform modifications to the tree,
|
|
except when initially populating the tree.
|
|
|
|
DeviceAction instances are registered, possibly causing the
|
|
addition or removal of Device instances to/from the tree. The
|
|
DeviceActions are all reversible up to the time their execute
|
|
method has been called.
|
|
|
|
Only one action of any given type/object pair should exist for
|
|
any given device at any given time.
|
|
|
|
DeviceAction instances can only be registered for leaf devices,
|
|
except for resize actions.
|
|
"""
|
|
|
|
def __init__(self, intf=None, ignored=[], exclusive=[], type=CLEARPART_TYPE_NONE,
|
|
clear=[], zeroMbr=None, reinitializeDisks=None, protected=[],
|
|
passphrase=None, luksDict=None, iscsi=None, dasd=None):
|
|
# internal data members
|
|
self._devices = []
|
|
self._actions = []
|
|
|
|
# indicates whether or not the tree has been fully populated
|
|
self.populated = False
|
|
|
|
self.intf = intf
|
|
self.exclusiveDisks = exclusive
|
|
self.clearPartType = type
|
|
self.clearPartDisks = clear
|
|
self.zeroMbr = zeroMbr
|
|
self.reinitializeDisks = reinitializeDisks
|
|
self.iscsi = iscsi
|
|
self.dasd = dasd
|
|
|
|
# protected device specs as provided by the user
|
|
self.protectedDevSpecs = protected
|
|
|
|
# names of protected devices at the time of tree population
|
|
self.protectedDevNames = []
|
|
|
|
self.unusedRaidMembers = []
|
|
|
|
self.__multipaths = {}
|
|
self.__multipathConfigWriter = devicelibs.mpath.MultipathConfigWriter()
|
|
|
|
self.__passphrase = passphrase
|
|
self.__luksDevs = {}
|
|
if luksDict and isinstance(luksDict, dict):
|
|
self.__luksDevs = luksDict
|
|
self._ignoredDisks = []
|
|
for disk in ignored:
|
|
self.addIgnoredDisk(disk)
|
|
self.immutableDevices = []
|
|
lvm.lvm_cc_resetFilter()
|
|
|
|
def addIgnoredDisk(self, disk):
|
|
self._ignoredDisks.append(disk)
|
|
lvm.lvm_cc_addFilterRejectRegexp(disk)
|
|
|
|
def pruneActions(self):
|
|
""" Prune loops and redundant actions from the queue. """
|
|
# handle device destroy actions
|
|
actions = self.findActions(type="destroy", object="device")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
destroys = self.findActions(devid=a.device.id,
|
|
type="destroy",
|
|
object="device")
|
|
|
|
creates = self.findActions(devid=a.device.id,
|
|
type="create",
|
|
object="device")
|
|
log.debug("found %d create and %d destroy actions for device id %d"
|
|
% (len(creates), len(destroys), a.device.id))
|
|
|
|
# If the device is not preexisting, we remove all actions up
|
|
# to and including the last destroy action.
|
|
# If the device is preexisting, we remove all actions from
|
|
# after the first destroy action up to and including the last
|
|
# destroy action.
|
|
# If the device is preexisting and there is only one device
|
|
# destroy action we remove all resize and format create/migrate
|
|
# actions on that device that precede the destroy action.
|
|
loops = []
|
|
first_destroy_idx = None
|
|
first_create_idx = None
|
|
stop_action = None
|
|
start = None
|
|
if len(destroys) > 1:
|
|
# there are multiple destroy actions for this device
|
|
loops = destroys
|
|
first_destroy_idx = self._actions.index(loops[0])
|
|
start = self._actions.index(a) + 1
|
|
stop_action = destroys[-1]
|
|
|
|
if creates:
|
|
first_create_idx = self._actions.index(creates[0])
|
|
if not loops or first_destroy_idx > first_create_idx:
|
|
# this device is not preexisting
|
|
start = first_create_idx
|
|
stop_action = destroys[-1]
|
|
|
|
dev_actions = self.findActions(devid=a.device.id)
|
|
if start is None:
|
|
# only one device destroy, so prune preceding resizes and
|
|
# format creates and migrates
|
|
for _a in dev_actions[:]:
|
|
if _a.isResize() or (_a.isFormat() and not _a.isDestroy()):
|
|
continue
|
|
|
|
dev_actions.remove(_a)
|
|
|
|
if not dev_actions:
|
|
# nothing to prune
|
|
continue
|
|
|
|
start = self._actions.index(dev_actions[0])
|
|
stop_action = dev_actions[-1]
|
|
|
|
# now we remove all actions on this device between the start
|
|
# index (into self._actions) and stop_action.
|
|
for rem in dev_actions:
|
|
end = self._actions.index(stop_action)
|
|
if start <= self._actions.index(rem) <= end:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
if rem == stop_action:
|
|
break
|
|
|
|
# device create actions
|
|
actions = self.findActions(type="create", object="device")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
creates = self.findActions(devid=a.device.id,
|
|
type="create",
|
|
object="device")
|
|
|
|
destroys = self.findActions(devid=a.device.id,
|
|
type="destroy",
|
|
object="device")
|
|
|
|
# If the device is preexisting, we remove everything between
|
|
# the first destroy and the last create.
|
|
# If the device is not preexisting, we remove everything up to
|
|
# the last create.
|
|
loops = []
|
|
first_destroy_idx = None
|
|
first_create_idx = None
|
|
stop_action = None
|
|
start = None
|
|
if len(creates) > 1:
|
|
# there are multiple create actions for this device
|
|
loops = creates
|
|
first_create_idx = self._actions.index(loops[0])
|
|
start = 0
|
|
stop_action = creates[-1]
|
|
|
|
if destroys:
|
|
first_destroy_idx = self._actions.index(destroys[0])
|
|
if not loops or first_create_idx > first_destroy_idx:
|
|
# this device is preexisting
|
|
start = first_destroy_idx + 1
|
|
stop_action = creates[-1]
|
|
|
|
if start is None:
|
|
continue
|
|
|
|
# remove all actions on this from after the first destroy up
|
|
# to the last create
|
|
dev_actions = self.findActions(devid=a.device.id)
|
|
for rem in dev_actions:
|
|
if rem == stop_action:
|
|
break
|
|
|
|
end = self._actions.index(stop_action)
|
|
if start <= self._actions.index(rem) < end:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
# device resize actions
|
|
actions = self.findActions(type="resize", object="device")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
loops = self.findActions(devid=a.device.id,
|
|
type="resize",
|
|
object="device")
|
|
|
|
if len(loops) == 1:
|
|
continue
|
|
|
|
# remove all but the last resize action on this device
|
|
for rem in loops[:-1]:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
# format destroy
|
|
# XXX I don't think there's a way for these loops to happen
|
|
actions = self.findActions(type="destroy", object="format")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
destroys = self.findActions(devid=a.device.id,
|
|
type="destroy",
|
|
object="format")
|
|
|
|
creates = self.findActions(devid=a.device.id,
|
|
type="create",
|
|
object="format")
|
|
|
|
# If the format is not preexisting, we remove all actions up
|
|
# to and including the last destroy action.
|
|
# If the format is preexisting, we remove all actions from
|
|
# after the first destroy action up to and including the last
|
|
# destroy action.
|
|
loops = []
|
|
first_destroy_idx = None
|
|
first_create_idx = None
|
|
stop_action = None
|
|
start = None
|
|
if len(destroys) > 1:
|
|
# there are multiple destroy actions for this format
|
|
loops = destroys
|
|
first_destroy_idx = self._actions.index(loops[0])
|
|
start = self._actions.index(a) + 1
|
|
stop_action = destroys[-1]
|
|
|
|
if creates:
|
|
first_create_idx = self._actions.index(creates[0])
|
|
if not loops or first_destroy_idx > first_create_idx:
|
|
# this format is not preexisting
|
|
start = first_create_idx
|
|
stop_action = destroys[-1]
|
|
|
|
if start is None:
|
|
continue
|
|
|
|
# now we remove all actions on this device's format between
|
|
# the start index (into self._actions) and stop_action.
|
|
dev_actions = self.findActions(devid=a.device.id,
|
|
object="format")
|
|
for rem in dev_actions:
|
|
end = self._actions.index(stop_action)
|
|
if start <= self._actions.index(rem) <= end:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
if rem == stop_action:
|
|
break
|
|
|
|
# format create
|
|
# XXX I don't think there's a way for these loops to happen
|
|
actions = self.findActions(type="create", object="format")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
creates = self.findActions(devid=a.device.id,
|
|
type="create",
|
|
object="format")
|
|
|
|
destroys = self.findActions(devid=a.device.id,
|
|
type="destroy",
|
|
object="format")
|
|
|
|
# If the format is preexisting, we remove everything between
|
|
# the first destroy and the last create.
|
|
# If the format is not preexisting, we remove everything up to
|
|
# the last create.
|
|
loops = []
|
|
first_destroy_idx = None
|
|
first_create_idx = None
|
|
stop_action = None
|
|
start = None
|
|
if len(creates) > 1:
|
|
# there are multiple create actions for this format
|
|
loops = creates
|
|
first_create_idx = self._actions.index(loops[0])
|
|
start = 0
|
|
stop_action = creates[-1]
|
|
|
|
if destroys:
|
|
first_destroy_idx = self._actions.index(destroys[0])
|
|
if not loops or first_create_idx > first_destroy_idx:
|
|
# this format is preexisting
|
|
start = first_destroy_idx + 1
|
|
stop_action = creates[-1]
|
|
|
|
if start is None:
|
|
continue
|
|
|
|
# remove all actions on this from after the first destroy up
|
|
# to the last create
|
|
dev_actions = self.findActions(devid=a.device.id,
|
|
object="format")
|
|
for rem in dev_actions:
|
|
if rem == stop_action:
|
|
break
|
|
|
|
end = self._actions.index(stop_action)
|
|
if start <= self._actions.index(rem) < end:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
# format resize
|
|
actions = self.findActions(type="resize", object="format")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
loops = self.findActions(devid=a.device.id,
|
|
type="resize",
|
|
object="format")
|
|
|
|
if len(loops) == 1:
|
|
continue
|
|
|
|
# remove all but the last resize action on this format
|
|
for rem in loops[:-1]:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
# format migrate
|
|
# XXX I don't think there's away for these loops to occur
|
|
actions = self.findActions(type="migrate", object="format")
|
|
for a in actions:
|
|
if a not in self._actions:
|
|
# we may have removed some of the actions in a previous
|
|
# iteration of this loop
|
|
continue
|
|
|
|
log.debug("action '%s' (%s)" % (a, id(a)))
|
|
loops = self.findActions(devid=a.device.id,
|
|
type="migrate",
|
|
object="format")
|
|
|
|
if len(loops) == 1:
|
|
continue
|
|
|
|
# remove all but the last migrate action on this format
|
|
for rem in loops[:-1]:
|
|
log.debug(" removing action '%s' (%s)" % (rem, id(rem)))
|
|
self._actions.remove(rem)
|
|
|
|
def processActions(self, dryRun=None):
|
|
""" Execute all registered actions. """
|
|
# in most cases the actions will already be sorted because of the
|
|
# rules for registration, but let's not rely on that
|
|
def cmpActions(a1, a2):
|
|
ret = 0
|
|
if a1.isDestroy() and a2.isDestroy():
|
|
if a1.device.path == a2.device.path:
|
|
# if it's the same device, destroy the format first
|
|
if a1.isFormat() and a2.isFormat():
|
|
ret = 0
|
|
elif a1.isFormat() and not a2.isFormat():
|
|
ret = -1
|
|
elif not a1.isFormat() and a2.isFormat():
|
|
ret = 1
|
|
elif a1.device.dependsOn(a2.device):
|
|
ret = -1
|
|
elif a2.device.dependsOn(a1.device):
|
|
ret = 1
|
|
# generally destroy partitions after lvs, vgs, &c
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
isinstance(a2.device, PartitionDevice):
|
|
if a1.device.disk == a2.device.disk:
|
|
ret = cmp(a2.device.partedPartition.number,
|
|
a1.device.partedPartition.number)
|
|
else:
|
|
ret = cmp(a2.device.name, a1.device.name)
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
a2.device.partitioned:
|
|
ret = 1
|
|
elif isinstance(a2.device, PartitionDevice) and \
|
|
a1.device.partitioned:
|
|
ret = -1
|
|
# remove partitions before unpartitioned non-partition
|
|
# devices
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
not isinstance(a2.device, PartitionDevice):
|
|
ret = 1
|
|
elif isinstance(a2.device, PartitionDevice) and \
|
|
not isinstance(a1.device, PartitionDevice):
|
|
ret = -1
|
|
else:
|
|
ret = 0
|
|
elif a1.isDestroy():
|
|
ret = -1
|
|
elif a2.isDestroy():
|
|
ret = 1
|
|
elif a1.isResize() and a2.isResize():
|
|
if a1.device.path == a2.device.path:
|
|
if a1.obj == a2.obj:
|
|
ret = 0
|
|
elif a1.isFormat() and not a2.isFormat():
|
|
# same path, one device, one format
|
|
if a1.isGrow():
|
|
ret = 1
|
|
else:
|
|
ret = -1
|
|
elif not a1.isFormat() and a2.isFormat():
|
|
# same path, one device, one format
|
|
if a1.isGrow():
|
|
ret = -1
|
|
else:
|
|
ret = 1
|
|
else:
|
|
ret = cmp(a1.device.name, a2.device.name)
|
|
elif a1.device.dependsOn(a2.device):
|
|
if a1.isGrow():
|
|
ret = 1
|
|
else:
|
|
ret = -1
|
|
elif a2.device.dependsOn(a1.device):
|
|
if a1.isGrow():
|
|
ret = -1
|
|
else:
|
|
ret = 1
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
isinstance(a2.device, PartitionDevice):
|
|
ret = cmp(a1.device.name, a2.device.name)
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
a2.device.partitioned:
|
|
if a1.isGrow():
|
|
ret = -1
|
|
else:
|
|
ret = 1
|
|
elif isinstance(a2.device, PartitionDevice) and \
|
|
a1.device.partitioned:
|
|
if a2.isGrow():
|
|
ret = 1
|
|
else:
|
|
ret = -1
|
|
else:
|
|
ret = 0
|
|
elif a1.isResize():
|
|
ret = -1
|
|
elif a2.isResize():
|
|
ret = 1
|
|
elif a1.isCreate() and a2.isCreate():
|
|
if a1.device.path == a2.device.path:
|
|
if a1.obj == a2.obj:
|
|
ret = 0
|
|
if a1.isFormat():
|
|
ret = 1
|
|
elif a2.isFormat():
|
|
ret = -1
|
|
else:
|
|
ret = 0
|
|
elif a1.device.dependsOn(a2.device):
|
|
ret = 1
|
|
elif a2.device.dependsOn(a1.device):
|
|
ret = -1
|
|
# generally create partitions before other device types
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
isinstance(a2.device, PartitionDevice):
|
|
if a1.device.disk == a2.device.disk:
|
|
ret = cmp(a1.device.partedPartition.number,
|
|
a2.device.partedPartition.number)
|
|
else:
|
|
ret = cmp(a1.device.name, a2.device.name)
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
a2.device.partitioned:
|
|
ret = 1
|
|
elif isinstance(a2.device, PartitionDevice) and \
|
|
a1.device.partitioned:
|
|
ret = -1
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
not isinstance(a2.device, PartitionDevice):
|
|
ret = -1
|
|
elif isinstance(a2.device, PartitionDevice) and \
|
|
not isinstance(a1.device, PartitionDevice):
|
|
ret = 1
|
|
else:
|
|
ret = 0
|
|
elif a1.isCreate():
|
|
ret = -1
|
|
elif a2.isCreate():
|
|
ret = 1
|
|
elif a1.isMigrate() and a2.isMigrate():
|
|
if a1.device.path == a2.device.path:
|
|
ret = 0
|
|
elif a1.device.dependsOn(a2.device):
|
|
ret = 1
|
|
elif a2.device.dependsOn(a1.device):
|
|
ret = -1
|
|
elif isinstance(a1.device, PartitionDevice) and \
|
|
isinstance(a2.device, PartitionDevice):
|
|
ret = cmp(a1.device.name, a2.device.name)
|
|
else:
|
|
ret = cmp(a1.device.name, a2.device.name)
|
|
else:
|
|
ret = 0
|
|
|
|
log.debug("cmp: %d -- %s | %s" % (ret, a1, a2))
|
|
return ret
|
|
|
|
log.debug("resetting parted disks...")
|
|
for device in self.devices:
|
|
if device.partitioned:
|
|
device.format.resetPartedDisk()
|
|
|
|
# reget parted.Partition for remaining preexisting devices
|
|
for device in self.devices:
|
|
if isinstance(device, PartitionDevice):
|
|
p = device.partedPartition
|
|
|
|
# reget parted.Partition for existing devices we're removing
|
|
for action in self._actions:
|
|
if isinstance(action.device, PartitionDevice):
|
|
p = action.device.partedPartition
|
|
|
|
# setup actions to create any extended partitions we added
|
|
#
|
|
# XXX At this point there can be duplicate partition paths in the
|
|
# tree (eg: non-existent sda6 and previous sda6 that will become
|
|
# sda5 in the course of partitioning), so we access the list
|
|
# directly here.
|
|
for device in self._devices:
|
|
if isinstance(device, PartitionDevice) and \
|
|
device.isExtended and not device.exists:
|
|
# don't properly register the action since the device is
|
|
# already in the tree
|
|
self._actions.append(ActionCreateDevice(device))
|
|
|
|
for action in self._actions:
|
|
log.debug("action: %s" % action)
|
|
|
|
log.debug("pruning action queue...")
|
|
self.pruneActions()
|
|
for action in self._actions:
|
|
log.debug("action: %s" % action)
|
|
|
|
log.debug("sorting actions...")
|
|
self._actions.sort(cmp=cmpActions)
|
|
for action in self._actions:
|
|
log.debug("action: %s" % action)
|
|
|
|
for action in self._actions:
|
|
log.info("executing action: %s" % action)
|
|
if not dryRun:
|
|
try:
|
|
action.execute(intf=self.intf)
|
|
except DiskLabelCommitError:
|
|
# it's likely that a previous format destroy action
|
|
# triggered setup of an lvm or md device.
|
|
self.teardownAll()
|
|
action.execute(intf=self.intf)
|
|
|
|
udev_settle()
|
|
for device in self._devices:
|
|
# make sure we catch any renumbering parted does
|
|
if device.exists and isinstance(device, PartitionDevice):
|
|
device.updateName()
|
|
device.format.device = device.path
|
|
|
|
def _addDevice(self, newdev):
|
|
""" Add a device to the tree.
|
|
|
|
Raise ValueError if the device's identifier is already
|
|
in the list.
|
|
"""
|
|
if newdev.path in [d.path for d in self._devices] and \
|
|
not isinstance(newdev, NoDevice):
|
|
raise ValueError("device is already in tree")
|
|
|
|
# make sure this device's parent devices are in the tree already
|
|
for parent in newdev.parents:
|
|
if parent not in self._devices:
|
|
raise DeviceTreeError("parent device not in tree")
|
|
|
|
self._devices.append(newdev)
|
|
log.debug("added %s %s (id %d) to device tree" % (newdev.type,
|
|
newdev.name,
|
|
newdev.id))
|
|
|
|
def _removeDevice(self, dev, force=None, moddisk=True):
|
|
""" Remove a device from the tree.
|
|
|
|
Only leaves may be removed.
|
|
"""
|
|
if dev not in self._devices:
|
|
raise ValueError("Device '%s' not in tree" % dev.name)
|
|
|
|
if not dev.isleaf and not force:
|
|
log.debug("%s has %d kids" % (dev.name, dev.kids))
|
|
raise ValueError("Cannot remove non-leaf device '%s'" % dev.name)
|
|
|
|
# if this is a partition we need to remove it from the parted.Disk
|
|
if moddisk and isinstance(dev, PartitionDevice) and \
|
|
dev.disk is not None:
|
|
# if this partition hasn't been allocated it could not have
|
|
# a disk attribute
|
|
if dev.partedPartition.type == parted.PARTITION_EXTENDED and \
|
|
len(dev.disk.format.logicalPartitions) > 0:
|
|
raise ValueError("Cannot remove extended partition %s. "
|
|
"Logical partitions present." % dev.name)
|
|
|
|
dev.disk.format.removePartition(dev.partedPartition)
|
|
|
|
# adjust all other PartitionDevice instances belonging to the
|
|
# same disk so the device name matches the potentially altered
|
|
# name of the parted.Partition
|
|
for device in self._devices:
|
|
if isinstance(device, PartitionDevice) and \
|
|
device.disk == dev.disk:
|
|
device.updateName()
|
|
|
|
self._devices.remove(dev)
|
|
log.debug("removed %s %s (id %d) from device tree" % (dev.type,
|
|
dev.name,
|
|
dev.id))
|
|
|
|
for parent in dev.parents:
|
|
# Will this cause issues with garbage collection?
|
|
# Do we care about garbage collection? At all?
|
|
parent.removeChild()
|
|
|
|
def registerAction(self, action):
|
|
""" Register an action to be performed at a later time.
|
|
|
|
Modifications to the Device instance are handled before we
|
|
get here.
|
|
"""
|
|
if (action.isDestroy() or action.isResize() or \
|
|
(action.isCreate() and action.isFormat())) and \
|
|
action.device not in self._devices:
|
|
raise DeviceTreeError("device is not in the tree")
|
|
elif (action.isCreate() and action.isDevice()):
|
|
# this allows multiple create actions w/o destroy in between;
|
|
# we will clean it up before processing actions
|
|
#raise DeviceTreeError("device is already in the tree")
|
|
if action.device in self._devices:
|
|
self._removeDevice(action.device)
|
|
for d in self._devices:
|
|
if d.path == action.device.path:
|
|
self._removeDevice(d)
|
|
|
|
if action.isCreate() and action.isDevice():
|
|
self._addDevice(action.device)
|
|
elif action.isDestroy() and action.isDevice():
|
|
self._removeDevice(action.device)
|
|
elif action.isCreate() and action.isFormat():
|
|
if isinstance(action.device.format, formats.fs.FS) and \
|
|
action.device.format.mountpoint in self.filesystems:
|
|
raise DeviceTreeError("mountpoint already in use")
|
|
|
|
log.debug("registered action: %s" % action)
|
|
self._actions.append(action)
|
|
|
|
def cancelAction(self, action):
|
|
""" Cancel a registered action.
|
|
|
|
This will unregister the action and do any required
|
|
modifications to the device list.
|
|
|
|
Actions all operate on a Device, so we can use the devices
|
|
to determine dependencies.
|
|
"""
|
|
if action.isCreate() and action.isDevice():
|
|
# remove the device from the tree
|
|
self._removeDevice(action.device)
|
|
elif action.isDestroy() and action.isDevice():
|
|
# add the device back into the tree
|
|
self._addDevice(action.device)
|
|
elif action.isFormat() and \
|
|
(action.isCreate() or action.isMigrate() or action.isResize()):
|
|
action.cancel()
|
|
|
|
self._actions.remove(action)
|
|
|
|
def findActions(self, device=None, type=None, object=None, path=None,
|
|
devid=None):
|
|
""" Find all actions that match all specified parameters.
|
|
|
|
Keyword arguments:
|
|
|
|
device -- device to match (Device, or None to match any)
|
|
type -- action type to match (string, or None to match any)
|
|
object -- operand type to match (string, or None to match any)
|
|
path -- device path to match (string, or None to match any)
|
|
|
|
"""
|
|
if device is None and type is None and object is None and \
|
|
path is None and devid is None:
|
|
return self._actions[:]
|
|
|
|
# convert the string arguments to the types used in actions
|
|
_type = action_type_from_string(type)
|
|
_object = action_object_from_string(object)
|
|
|
|
actions = []
|
|
for action in self._actions:
|
|
if device is not None and action.device != device:
|
|
continue
|
|
|
|
if _type is not None and action.type != _type:
|
|
continue
|
|
|
|
if _object is not None and action.obj != _object:
|
|
continue
|
|
|
|
if path is not None and action.device.path != path:
|
|
continue
|
|
|
|
if devid is not None and action.device.id != devid:
|
|
continue
|
|
|
|
actions.append(action)
|
|
|
|
return actions
|
|
|
|
def getDependentDevices(self, dep):
|
|
""" Return a list of devices that depend on dep.
|
|
|
|
The list includes both direct and indirect dependents.
|
|
"""
|
|
dependents = []
|
|
|
|
# special handling for extended partitions since the logical
|
|
# partitions and their deps effectively depend on the extended
|
|
logicals = []
|
|
if isinstance(dep, PartitionDevice) and dep.partType and \
|
|
dep.isExtended:
|
|
# collect all of the logicals on the same disk
|
|
for part in self.getDevicesByInstance(PartitionDevice):
|
|
if part.partType and part.isLogical and part.disk == dep.disk:
|
|
logicals.append(part)
|
|
|
|
for device in self.devices:
|
|
if device.dependsOn(dep):
|
|
dependents.append(device)
|
|
else:
|
|
for logical in logicals:
|
|
if device.dependsOn(logical):
|
|
dependents.append(device)
|
|
break
|
|
|
|
return dependents
|
|
|
|
def isIgnored(self, info):
|
|
""" Return True if info is a device we should ignore.
|
|
|
|
Arguments:
|
|
|
|
info -- a dict representing a udev db entry
|
|
|
|
TODO:
|
|
|
|
- filtering of SAN/FC devices
|
|
- filtering by driver?
|
|
|
|
"""
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
name = udev_device_get_name(info)
|
|
if not sysfs_path:
|
|
return None
|
|
|
|
if name in self._ignoredDisks:
|
|
return True
|
|
|
|
# Special handling for mdraid external metadata sets (mdraid BIOSRAID):
|
|
# 1) The containers are intermediate devices which will never be
|
|
# in exclusiveDisks
|
|
# 2) Sets get added to exclusive disks with their dmraid set name by
|
|
# the filter ui. Note that making the ui use md names instead is not
|
|
# possible as the md names are simpy md# and we cannot predict the #
|
|
if udev_device_get_md_level(info) == "container":
|
|
return False
|
|
|
|
if udev_device_get_md_container(info) and \
|
|
udev_device_get_md_name(info):
|
|
md_name = udev_device_get_md_name(info)
|
|
for i in range(0, len(self.exclusiveDisks)):
|
|
if re.match("isw_[a-z]*_%s" % md_name, self.exclusiveDisks[i]):
|
|
self.exclusiveDisks[i] = name
|
|
return False
|
|
|
|
if udev_device_is_disk(info) and \
|
|
not udev_device_is_md(info) and \
|
|
not udev_device_is_dm(info) and \
|
|
not udev_device_is_biosraid(info) and \
|
|
not udev_device_is_multipath_member(info):
|
|
if self.exclusiveDisks and name not in self.exclusiveDisks:
|
|
self.addIgnoredDisk(name)
|
|
return True
|
|
|
|
# Ignore loop and ram devices, we normally already skip these in
|
|
# udev.py: enumerate_block_devices(), but we can still end up trying
|
|
# to add them to the tree when they are slaves of other devices, this
|
|
# happens for example with the livecd
|
|
if name.startswith("loop") or name.startswith("ram"):
|
|
return True
|
|
|
|
# FIXME: check for virtual devices whose slaves are on the ignore list
|
|
|
|
def addUdevDMDevice(self, info):
|
|
name = udev_device_get_name(info)
|
|
log_method_call(self, name=name)
|
|
uuid = udev_device_get_uuid(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
device = None
|
|
|
|
for dmdev in self.devices:
|
|
if not isinstance(dmdev, DMDevice):
|
|
continue
|
|
|
|
try:
|
|
# there is a device in the tree already with the same
|
|
# major/minor as this one but with a different name
|
|
# XXX this is kind of racy
|
|
if dmdev.getDMNode() == os.path.basename(sysfs_path):
|
|
# XXX should we take the name already in use?
|
|
device = dmdev
|
|
break
|
|
except DMError:
|
|
# This is a little lame, but the VG device is a DMDevice
|
|
# and it won't have a dm node. At any rate, this is not
|
|
# important enough to crash the install.
|
|
log.debug("failed to find dm node for %s" % dmdev.name)
|
|
continue
|
|
|
|
if device is None:
|
|
# we couldn't find it, so create it
|
|
# first, get a list of the slave devs and look them up
|
|
slaves = []
|
|
dir = os.path.normpath("/sys/%s/slaves" % sysfs_path)
|
|
slave_names = os.listdir(dir)
|
|
for slave_name in slave_names:
|
|
# if it's a dm-X name, resolve it to a map name first
|
|
if slave_name.startswith("dm-"):
|
|
dev_name = dm.name_from_dm_node(slave_name)
|
|
else:
|
|
dev_name = slave_name
|
|
slave_dev = self.getDeviceByName(dev_name)
|
|
if slave_dev:
|
|
slaves.append(slave_dev)
|
|
else:
|
|
# we haven't scanned the slave yet, so do it now
|
|
path = os.path.normpath("%s/%s" % (dir, slave_name))
|
|
new_info = udev_get_block_device(os.path.realpath(path)[4:])
|
|
if new_info:
|
|
self.addUdevDevice(new_info)
|
|
if self.getDeviceByName(dev_name) is None:
|
|
# if the current slave is still not in
|
|
# the tree, something has gone wrong
|
|
log.error("failure scanning device %s: could not add slave %s" % (name, dev_name))
|
|
return
|
|
|
|
# try to get the device again now that we've got all the slaves
|
|
device = self.getDeviceByName(name)
|
|
|
|
if device is None:
|
|
if udev_device_is_multipath_partition(info, self):
|
|
diskname = udev_device_get_multipath_partition_disk(info)
|
|
disk = self.getDeviceByName(diskname)
|
|
return self.addUdevPartitionDevice(info, disk=disk)
|
|
elif udev_device_is_dmraid_partition(info, self):
|
|
diskname = udev_device_get_dmraid_partition_disk(info)
|
|
disk = self.getDeviceByName(diskname)
|
|
return self.addUdevPartitionDevice(info, disk=disk)
|
|
|
|
# if we get here, we found all of the slave devices and
|
|
# something must be wrong -- if all of the slaves are in
|
|
# the tree, this device should be as well
|
|
if device is None:
|
|
log.warning("ignoring dm device %s" % name)
|
|
|
|
return device
|
|
|
|
def addUdevMDDevice(self, info):
|
|
name = udev_device_get_name(info)
|
|
log_method_call(self, name=name)
|
|
uuid = udev_device_get_uuid(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
device = None
|
|
|
|
slaves = []
|
|
dir = os.path.normpath("/sys/%s/slaves" % sysfs_path)
|
|
slave_names = os.listdir(dir)
|
|
for slave_name in slave_names:
|
|
# if it's a dm-X name, resolve it to a map name
|
|
if slave_name.startswith("dm-"):
|
|
dev_name = dm.name_from_dm_node(slave_name)
|
|
else:
|
|
dev_name = slave_name
|
|
slave_dev = self.getDeviceByName(dev_name)
|
|
if slave_dev:
|
|
slaves.append(slave_dev)
|
|
else:
|
|
# we haven't scanned the slave yet, so do it now
|
|
path = os.path.normpath("%s/%s" % (dir, slave_name))
|
|
new_info = udev_get_block_device(os.path.realpath(path)[4:])
|
|
if new_info:
|
|
self.addUdevDevice(new_info)
|
|
if self.getDeviceByName(dev_name) is None:
|
|
# if the current slave is still not in
|
|
# the tree, something has gone wrong
|
|
log.error("failure scanning device %s: could not add slave %s" % (name, dev_name))
|
|
return
|
|
|
|
# try to get the device again now that we've got all the slaves
|
|
device = self.getDeviceByName(name)
|
|
|
|
# if we get here, we found all of the slave devices and
|
|
# something must be wrong -- if all of the slaves we in
|
|
# the tree, this device should be as well
|
|
if device is None:
|
|
log.warning("using MD RAID device for %s" % name)
|
|
try:
|
|
# level is reported as, eg: "raid1"
|
|
md_level = udev_device_get_md_level(info)
|
|
md_devices = int(udev_device_get_md_devices(info))
|
|
md_uuid = udev_device_get_md_uuid(info)
|
|
except (KeyError, IndexError, ValueError) as e:
|
|
log.warning("invalid data for %s: %s" % (name, e))
|
|
return
|
|
|
|
device = MDRaidArrayDevice(name,
|
|
level=md_level,
|
|
memberDevices=md_devices,
|
|
uuid=md_uuid,
|
|
exists=True,
|
|
parents=slaves)
|
|
self._addDevice(device)
|
|
|
|
return device
|
|
|
|
def addUdevPartitionDevice(self, info, disk=None):
|
|
name = udev_device_get_name(info)
|
|
log_method_call(self, name=name)
|
|
uuid = udev_device_get_uuid(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
device = None
|
|
|
|
if disk is None:
|
|
disk_name = os.path.basename(os.path.dirname(sysfs_path))
|
|
disk_name = disk_name.replace('!','/')
|
|
disk = self.getDeviceByName(disk_name)
|
|
|
|
if disk is None:
|
|
# create a device instance for the disk
|
|
new_info = udev_get_block_device(os.path.dirname(sysfs_path))
|
|
if new_info:
|
|
self.addUdevDevice(new_info)
|
|
disk = self.getDeviceByName(disk_name)
|
|
|
|
if disk is None:
|
|
# if the current device is still not in
|
|
# the tree, something has gone wrong
|
|
log.error("failure scanning device %s" % disk_name)
|
|
lvm.lvm_cc_addFilterRejectRegexp(name)
|
|
return
|
|
|
|
# Check that the disk has partitions. If it does not, we must have
|
|
# reinitialized the disklabel.
|
|
#
|
|
# Also ignore partitions on devices we do not support partitioning
|
|
# of, like logical volumes.
|
|
if not getattr(disk.format, "partitions", None) or \
|
|
not disk.partitionable:
|
|
# When we got here because the disk does not have a disklabel
|
|
# format (ie a biosraid member), or because it is not
|
|
# partitionable we want LVM to ignore this partition too
|
|
if disk.format.type != "disklabel" or not disk.partitionable:
|
|
lvm.lvm_cc_addFilterRejectRegexp(name)
|
|
log.debug("ignoring partition %s" % name)
|
|
return
|
|
|
|
try:
|
|
device = PartitionDevice(name, sysfsPath=sysfs_path,
|
|
major=udev_device_get_major(info),
|
|
minor=udev_device_get_minor(info),
|
|
exists=True, parents=[disk])
|
|
except DeviceError:
|
|
# corner case sometime the kernel accepts a partition table
|
|
# which gets rejected by parted, in this case we will
|
|
# prompt to re-initialize the disk, so simply skip the
|
|
# faulty partitions.
|
|
return
|
|
|
|
self._addDevice(device)
|
|
return device
|
|
|
|
def addUdevDiskDevice(self, info):
|
|
name = udev_device_get_name(info)
|
|
log_method_call(self, name=name)
|
|
uuid = udev_device_get_uuid(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
serial = udev_device_get_serial(info)
|
|
bus = udev_device_get_bus(info)
|
|
|
|
# udev doesn't always provide a vendor.
|
|
vendor = udev_device_get_vendor(info)
|
|
if not vendor:
|
|
vendor = ""
|
|
|
|
device = None
|
|
|
|
kwargs = { "serial": serial, "vendor": vendor, "bus": bus }
|
|
if udev_device_is_iscsi(info):
|
|
diskType = iScsiDiskDevice
|
|
kwargs["node"] = self.iscsi.getNode(
|
|
udev_device_get_iscsi_name(info),
|
|
udev_device_get_iscsi_address(info),
|
|
udev_device_get_iscsi_port(info))
|
|
kwargs["ibft"] = kwargs["node"] in self.iscsi.ibftNodes
|
|
kwargs["initiator"] = self.iscsi.initiator
|
|
log.debug("%s is an iscsi disk" % name)
|
|
elif udev_device_is_fcoe(info):
|
|
diskType = FcoeDiskDevice
|
|
kwargs["nic"] = udev_device_get_fcoe_nic(info)
|
|
kwargs["identifier"] = udev_device_get_fcoe_identifier(info)
|
|
log.debug("%s is an fcoe disk" % name)
|
|
elif udev_device_get_md_container(info):
|
|
diskType = MDRaidArrayDevice
|
|
parentName = devicePathToName(udev_device_get_md_container(info))
|
|
kwargs["parents"] = [ self.getDeviceByName(parentName) ]
|
|
kwargs["level"] = udev_device_get_md_level(info)
|
|
kwargs["memberDevices"] = int(udev_device_get_md_devices(info))
|
|
kwargs["uuid"] = udev_device_get_md_uuid(info)
|
|
kwargs["exists"] = True
|
|
del kwargs["serial"]
|
|
del kwargs["vendor"]
|
|
del kwargs["bus"]
|
|
elif udev_device_is_dasd(info):
|
|
diskType = DASDDevice
|
|
kwargs["dasd"] = self.dasd
|
|
kwargs["busid"] = udev_device_get_dasd_bus_id(info)
|
|
kwargs["opts"] = {}
|
|
|
|
for attr in ['readonly', 'use_diag', 'erplog', 'failfast']:
|
|
kwargs["opts"][attr] = udev_device_get_dasd_flag(info, attr)
|
|
|
|
log.debug("%s is a dasd device" % name)
|
|
elif udev_device_is_zfcp(info):
|
|
diskType = ZFCPDiskDevice
|
|
|
|
for attr in ['hba_id', 'wwpn', 'fcp_lun']:
|
|
kwargs[attr] = udev_device_get_zfcp_attribute(info, attr=attr)
|
|
|
|
log.debug("%s is a zfcp device" % name)
|
|
else:
|
|
diskType = DiskDevice
|
|
log.debug("%s is a disk" % name)
|
|
|
|
device = diskType(name,
|
|
major=udev_device_get_major(info),
|
|
minor=udev_device_get_minor(info),
|
|
sysfsPath=sysfs_path, **kwargs)
|
|
self._addDevice(device)
|
|
return device
|
|
|
|
def addUdevOpticalDevice(self, info):
|
|
log_method_call(self)
|
|
# XXX should this be RemovableDevice instead?
|
|
#
|
|
# Looks like if it has ID_INSTANCE=0:1 we can ignore it.
|
|
device = OpticalDevice(udev_device_get_name(info),
|
|
major=udev_device_get_major(info),
|
|
minor=udev_device_get_minor(info),
|
|
sysfsPath=udev_device_get_sysfs_path(info),
|
|
vendor=udev_device_get_vendor(info),
|
|
model=udev_device_get_model(info))
|
|
self._addDevice(device)
|
|
return device
|
|
|
|
def addUdevDevice(self, info):
|
|
name = udev_device_get_name(info)
|
|
log_method_call(self, name=name, info=info)
|
|
uuid = udev_device_get_uuid(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
|
|
if self.isIgnored(info):
|
|
log.debug("ignoring %s (%s)" % (name, sysfs_path))
|
|
return
|
|
|
|
log.debug("scanning %s (%s)..." % (name, sysfs_path))
|
|
device = self.getDeviceByName(name)
|
|
|
|
#
|
|
# The first step is to either look up or create the device
|
|
#
|
|
if udev_device_is_multipath_member(info):
|
|
device = DiskDevice(name,
|
|
major=udev_device_get_major(info),
|
|
minor=udev_device_get_minor(info),
|
|
sysfsPath=sysfs_path, exists=True,
|
|
serial=udev_device_get_serial(info),
|
|
vendor=udev_device_get_vendor(info),
|
|
model=udev_device_get_model(info))
|
|
self._addDevice(device)
|
|
elif udev_device_is_dm(info) and \
|
|
devicelibs.dm.dm_is_multipath(info):
|
|
log.debug("%s is a multipath device" % name)
|
|
self.addUdevDMDevice(info)
|
|
elif udev_device_is_dm(info):
|
|
log.debug("%s is a device-mapper device" % name)
|
|
# try to look up the device
|
|
if device is None and uuid:
|
|
# try to find the device by uuid
|
|
device = self.getDeviceByUuid(uuid)
|
|
|
|
if device is None:
|
|
device = self.addUdevDMDevice(info)
|
|
elif udev_device_is_md(info):
|
|
log.debug("%s is an md device" % name)
|
|
if device is None and uuid:
|
|
# try to find the device by uuid
|
|
device = self.getDeviceByUuid(uuid)
|
|
|
|
if device is None:
|
|
device = self.addUdevMDDevice(info)
|
|
elif udev_device_is_cdrom(info):
|
|
log.debug("%s is a cdrom" % name)
|
|
if device is None:
|
|
device = self.addUdevOpticalDevice(info)
|
|
elif udev_device_is_biosraid(info) and udev_device_is_disk(info):
|
|
log.debug("%s is part of a biosraid" % name)
|
|
if device is None:
|
|
device = DiskDevice(name,
|
|
major=udev_device_get_major(info),
|
|
minor=udev_device_get_minor(info),
|
|
sysfsPath=sysfs_path, exists=True)
|
|
self._addDevice(device)
|
|
elif udev_device_is_disk(info):
|
|
if device is None:
|
|
device = self.addUdevDiskDevice(info)
|
|
elif udev_device_is_partition(info):
|
|
log.debug("%s is a partition" % name)
|
|
if device is None:
|
|
device = self.addUdevPartitionDevice(info)
|
|
else:
|
|
log.error("Unknown block device type for: %s" % name)
|
|
return
|
|
|
|
# If this device is protected, mark it as such now. Once the tree
|
|
# has been populated, devices' protected attribute is how we will
|
|
# identify protected devices.
|
|
if device and device.name in self.protectedDevNames:
|
|
device.protected = True
|
|
|
|
# Don't try to do format handling on drives without media or
|
|
# if we didn't end up with a device somehow.
|
|
if not device or not device.mediaPresent:
|
|
return
|
|
|
|
# now handle the device's formatting
|
|
self.handleUdevDeviceFormat(info, device)
|
|
log.debug("got device: %s" % device)
|
|
if device.format.type:
|
|
log.debug("got format: %s" % device.format)
|
|
device.originalFormat = device.format
|
|
|
|
def handleUdevDiskLabelFormat(self, info, device):
|
|
log_method_call(self, device=device.name)
|
|
if device.partitioned:
|
|
# this device is already set up
|
|
log.debug("disklabel format on %s already set up" % device.name)
|
|
return
|
|
|
|
try:
|
|
device.setup()
|
|
except Exception as e:
|
|
log.debug("setup of %s failed: %s" % (device.name, e))
|
|
log.warning("aborting disklabel handler for %s" % device.name)
|
|
return
|
|
|
|
# special handling for unsupported partitioned devices
|
|
if not device.partitionable:
|
|
try:
|
|
format = getFormat("disklabel",
|
|
device=device.path,
|
|
exists=True)
|
|
except InvalidDiskLabelError:
|
|
pass
|
|
else:
|
|
if format.partitions:
|
|
# parted's checks for disklabel presence are less than
|
|
# rigorous, so we will assume that detected disklabels
|
|
# with no partitions are spurious
|
|
device.format = format
|
|
return
|
|
|
|
# if the disk contains protected partitions we will not wipe the
|
|
# disklabel even if clearpart --initlabel was specified
|
|
if not self.clearPartDisks or device.name in self.clearPartDisks:
|
|
initlabel = self.reinitializeDisks
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
for protected in self.protectedDevNames:
|
|
# check for protected partition
|
|
_p = "/sys/%s/%s" % (sysfs_path, protected)
|
|
if os.path.exists(os.path.normpath(_p)):
|
|
initlabel = False
|
|
break
|
|
|
|
# check for protected partition on a device-mapper disk
|
|
disk_name = re.sub(r'p\d+$', '', protected)
|
|
if disk_name != protected and disk_name == device.name:
|
|
initlabel = False
|
|
break
|
|
else:
|
|
initlabel = False
|
|
|
|
|
|
if self.zeroMbr:
|
|
initcb = lambda: True
|
|
else:
|
|
path = device.path
|
|
description = device.description or device.model
|
|
bypath = os.path.basename(deviceNameToDiskByPath(path))
|
|
if bypath:
|
|
details = "\n\nDevice details:\n%s" % (bypath,)
|
|
else:
|
|
details = ""
|
|
|
|
initcb = lambda: self.intf.questionInitializeDisk(path,
|
|
description,
|
|
device.size,
|
|
details)
|
|
|
|
try:
|
|
format = getFormat("disklabel",
|
|
device=device.path,
|
|
exists=not initlabel)
|
|
except InvalidDiskLabelError:
|
|
# if there is preexisting formatting on the device we will
|
|
# use it instead of ignoring the device
|
|
if not self.zeroMbr and \
|
|
getFormat(udev_device_get_format(info)).type is not None:
|
|
return
|
|
# if we have a cb function use it. else we ignore the device.
|
|
if initcb is not None and initcb():
|
|
format = getFormat("disklabel",
|
|
device=device.path,
|
|
exists=False)
|
|
else:
|
|
self._removeDevice(device)
|
|
self.addIgnoredDisk(device.name)
|
|
return
|
|
|
|
if not format.exists:
|
|
# if we just initialized a disklabel we should schedule
|
|
# actions for destruction of the previous format and creation
|
|
# of the new one
|
|
self.registerAction(ActionDestroyFormat(device))
|
|
self.registerAction(ActionCreateFormat(device, format))
|
|
|
|
# If this is a mac-formatted disk we just initialized, make
|
|
# sure the partition table partition gets added to the device
|
|
# tree.
|
|
if device.format.partedDisk.type == "mac" and \
|
|
len(device.format.partitions) == 1:
|
|
name = device.format.partitions[0].getDeviceNodeName()
|
|
if not self.getDeviceByName(name):
|
|
partDevice = PartitionDevice(name, exists=True,
|
|
parents=[device])
|
|
self._addDevice(partDevice)
|
|
|
|
else:
|
|
device.format = format
|
|
|
|
def handleUdevLUKSFormat(self, info, device):
|
|
log_method_call(self, name=device.name, type=device.format.type)
|
|
if not device.format.uuid:
|
|
log.info("luks device %s has no uuid" % device.path)
|
|
return
|
|
|
|
# look up or create the mapped device
|
|
if not self.getDeviceByName(device.format.mapName):
|
|
passphrase = self.__luksDevs.get(device.format.uuid)
|
|
if passphrase:
|
|
device.format.passphrase = passphrase
|
|
else:
|
|
(passphrase, isglobal) = getLUKSPassphrase(self.intf,
|
|
device,
|
|
self.__passphrase)
|
|
if isglobal and device.format.status:
|
|
self.__passphrase = passphrase
|
|
|
|
luks_device = LUKSDevice(device.format.mapName,
|
|
parents=[device],
|
|
exists=True)
|
|
try:
|
|
luks_device.setup()
|
|
except (LUKSError, CryptoError, DeviceError) as e:
|
|
log.info("setup of %s failed: %s" % (device.format.mapName,
|
|
e))
|
|
device.removeChild()
|
|
else:
|
|
self._addDevice(luks_device)
|
|
else:
|
|
log.warning("luks device %s already in the tree"
|
|
% device.format.mapName)
|
|
|
|
def handleUdevLVMPVFormat(self, info, device):
|
|
log_method_call(self, name=device.name, type=device.format.type)
|
|
# lookup/create the VG and LVs
|
|
try:
|
|
vg_name = udev_device_get_vg_name(info)
|
|
except KeyError:
|
|
# no vg name means no vg -- we're done with this pv
|
|
return
|
|
|
|
vg_device = self.getDeviceByName(vg_name)
|
|
if vg_device:
|
|
vg_device._addDevice(device)
|
|
for lv in vg_device.lvs:
|
|
try:
|
|
lv.setup()
|
|
except DeviceError as (msg, name):
|
|
log.info("setup of %s failed: %s" % (lv.name, msg))
|
|
else:
|
|
try:
|
|
vg_uuid = udev_device_get_vg_uuid(info)
|
|
vg_size = udev_device_get_vg_size(info)
|
|
vg_free = udev_device_get_vg_free(info)
|
|
pe_size = udev_device_get_vg_extent_size(info)
|
|
pe_count = udev_device_get_vg_extent_count(info)
|
|
pe_free = udev_device_get_vg_free_extents(info)
|
|
pv_count = udev_device_get_vg_pv_count(info)
|
|
except (KeyError, ValueError) as e:
|
|
log.warning("invalid data for %s: %s" % (device.name, e))
|
|
return
|
|
|
|
vg_device = LVMVolumeGroupDevice(vg_name,
|
|
device,
|
|
uuid=vg_uuid,
|
|
size=vg_size,
|
|
free=vg_free,
|
|
peSize=pe_size,
|
|
peCount=pe_count,
|
|
peFree=pe_free,
|
|
pvCount=pv_count,
|
|
exists=True)
|
|
self._addDevice(vg_device)
|
|
|
|
try:
|
|
lv_names = udev_device_get_lv_names(info)
|
|
lv_uuids = udev_device_get_lv_uuids(info)
|
|
lv_sizes = udev_device_get_lv_sizes(info)
|
|
lv_attr = udev_device_get_lv_attr(info)
|
|
except KeyError as e:
|
|
log.warning("invalid data for %s: %s" % (device.name, e))
|
|
return
|
|
|
|
if not lv_names:
|
|
log.debug("no LVs listed for VG %s" % device.name)
|
|
return
|
|
|
|
# make a list of indices with snapshots at the end
|
|
indices = range(len(lv_names))
|
|
indices.sort(key=lambda i: lv_attr[i][0] in 'Ss')
|
|
for index in indices:
|
|
lv_name = lv_names[index]
|
|
name = "%s-%s" % (vg_name, lv_name)
|
|
if lv_attr[index][0] in 'Ss':
|
|
log.debug("found lvm snapshot volume '%s'" % name)
|
|
origin_name = devicelibs.lvm.lvorigin(vg_name, lv_name)
|
|
if not origin_name:
|
|
log.error("lvm snapshot '%s-%s' has unknown origin"
|
|
% (vg_name, lv_name))
|
|
continue
|
|
|
|
origin = self.getDeviceByName("%s-%s" % (vg_name,
|
|
origin_name))
|
|
if not origin:
|
|
log.warning("snapshot lv '%s' origin lv '%s-%s' "
|
|
"not found" % (name,
|
|
vg_name, origin_name))
|
|
continue
|
|
|
|
log.debug("adding %dMB to %s snapshot total"
|
|
% (lv_sizes[index], origin.name))
|
|
origin.snapshotSpace += lv_sizes[index]
|
|
continue
|
|
elif lv_attr[index][0] in 'Iil':
|
|
# skip mirror images and log volumes
|
|
continue
|
|
|
|
log_size = 0
|
|
if lv_attr[index][0] in 'Mm':
|
|
stripes = 0
|
|
# identify mirror stripes/copies and mirror logs
|
|
for (j, _lvname) in enumerate(lv_names):
|
|
if lv_attr[j][0] not in 'Iil':
|
|
continue
|
|
|
|
if _lvname == "[%s_mlog]" % lv_name:
|
|
log_size = lv_sizes[j]
|
|
elif _lvname.startswith("[%s_mimage_" % lv_name):
|
|
stripes += 1
|
|
else:
|
|
stripes = 1
|
|
|
|
lv_dev = self.getDeviceByName(name)
|
|
if lv_dev is None:
|
|
lv_uuid = lv_uuids[index]
|
|
lv_size = lv_sizes[index]
|
|
lv_device = LVMLogicalVolumeDevice(lv_name,
|
|
vg_device,
|
|
uuid=lv_uuid,
|
|
size=lv_size,
|
|
stripes=stripes,
|
|
logSize=log_size,
|
|
exists=True)
|
|
self._addDevice(lv_device)
|
|
|
|
try:
|
|
lv_device.setup()
|
|
except DeviceError as (msg, name):
|
|
log.info("setup of %s failed: %s"
|
|
% (lv_device.name, msg))
|
|
|
|
def handleUdevMDMemberFormat(self, info, device):
|
|
log_method_call(self, name=device.name, type=device.format.type)
|
|
# either look up or create the array device
|
|
name = udev_device_get_name(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
|
|
if udev_device_is_biosraid(info):
|
|
# this will prevent display of the member devices in the UI
|
|
device.format.biosraid = True
|
|
|
|
md_array = self.getDeviceByUuid(device.format.mdUuid)
|
|
if device.format.mdUuid and md_array:
|
|
md_array._addDevice(device)
|
|
else:
|
|
# create the array with just this one member
|
|
# FIXME: why does this exact block appear twice?
|
|
try:
|
|
# level is reported as, eg: "raid1"
|
|
md_level = udev_device_get_md_level(info)
|
|
md_devices = int(udev_device_get_md_devices(info))
|
|
md_uuid = udev_device_get_md_uuid(info)
|
|
except (KeyError, ValueError) as e:
|
|
log.warning("invalid data for %s: %s" % (name, e))
|
|
return
|
|
|
|
# try to name the array based on the preferred minor
|
|
md_info = devicelibs.mdraid.mdexamine(device.path)
|
|
md_path = md_info.get("device", "")
|
|
md_name = devicePathToName(md_info.get("device", ""))
|
|
if md_name:
|
|
try:
|
|
minor = int(md_name[2:]) # strip off leading "md"
|
|
except (IndexError, ValueError):
|
|
minor = None
|
|
md_name = None
|
|
else:
|
|
array = self.getDeviceByName(md_name)
|
|
if array and array.uuid != md_uuid:
|
|
md_name = None
|
|
|
|
if not md_name:
|
|
# if we don't have a name yet, find the first unused minor
|
|
minor = 0
|
|
while True:
|
|
if self.getDeviceByName("md%d" % minor):
|
|
minor += 1
|
|
else:
|
|
break
|
|
|
|
md_name = "md%d" % minor
|
|
|
|
log.debug("using name %s for md array containing member %s"
|
|
% (md_name, device.name))
|
|
md_array = MDRaidArrayDevice(md_name,
|
|
level=md_level,
|
|
minor=minor,
|
|
memberDevices=md_devices,
|
|
uuid=md_uuid,
|
|
sysfsPath=sysfs_path,
|
|
exists=True)
|
|
md_array._addDevice(device)
|
|
self._addDevice(md_array)
|
|
|
|
def handleMultipathMemberFormat(self, info, device):
|
|
log_method_call(self, name=device.name, type=device.format.type)
|
|
|
|
name = udev_device_get_multipath_name(info)
|
|
if self.__multipaths.has_key(name):
|
|
mp = self.__multipaths[name]
|
|
mp.addParent(device)
|
|
else:
|
|
mp = MultipathDevice(name, info, parents=[device])
|
|
self.__multipaths[name] = mp
|
|
|
|
def handleUdevDMRaidMemberFormat(self, info, device):
|
|
log_method_call(self, name=device.name, type=device.format.type)
|
|
name = udev_device_get_name(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
uuid = udev_device_get_uuid(info)
|
|
major = udev_device_get_major(info)
|
|
minor = udev_device_get_minor(info)
|
|
|
|
def _all_ignored(rss):
|
|
retval = True
|
|
for rs in rss:
|
|
if rs.name not in self._ignoredDisks:
|
|
retval = False
|
|
break
|
|
return retval
|
|
|
|
# Have we already created the DMRaidArrayDevice?
|
|
rss = block.getRaidSetFromRelatedMem(uuid=uuid, name=name,
|
|
major=major, minor=minor)
|
|
if len(rss) == 0:
|
|
# we ignore the device in the hope that all the devices
|
|
# from this set will be ignored.
|
|
self.unusedRaidMembers.append(device.name)
|
|
self.addIgnoredDisk(device.name)
|
|
return
|
|
|
|
# We ignore the device if all the rss are in self._ignoredDisks
|
|
if _all_ignored(rss):
|
|
self.addIgnoredDisk(device.name)
|
|
return
|
|
|
|
for rs in rss:
|
|
dm_array = self.getDeviceByName(rs.name)
|
|
if dm_array is not None:
|
|
# We add the new device.
|
|
dm_array._addDevice(device)
|
|
else:
|
|
# Activate the Raid set.
|
|
rs.activate(mknod=True)
|
|
dm_array = DMRaidArrayDevice(rs.name,
|
|
raidSet=rs,
|
|
parents=[device])
|
|
|
|
self._addDevice(dm_array)
|
|
|
|
# Wait for udev to scan the just created nodes, to avoid a race
|
|
# with the udev_get_block_device() call below.
|
|
udev_settle()
|
|
|
|
# Get the DMRaidArrayDevice a DiskLabel format *now*, in case
|
|
# its partitions get scanned before it does.
|
|
dm_array.updateSysfsPath()
|
|
dm_array_info = udev_get_block_device(dm_array.sysfsPath)
|
|
self.handleUdevDiskLabelFormat(dm_array_info, dm_array)
|
|
|
|
# Use the rs's object on the device.
|
|
# pyblock can return the memebers of a set and the
|
|
# device has the attribute to hold it. But ATM we
|
|
# are not really using it. Commenting this out until
|
|
# we really need it.
|
|
#device.format.raidmem = block.getMemFromRaidSet(dm_array,
|
|
# major=major, minor=minor, uuid=uuid, name=name)
|
|
|
|
def handleUdevDeviceFormat(self, info, device):
|
|
log_method_call(self, name=getattr(device, "name", None))
|
|
name = udev_device_get_name(info)
|
|
sysfs_path = udev_device_get_sysfs_path(info)
|
|
uuid = udev_device_get_uuid(info)
|
|
label = udev_device_get_label(info)
|
|
format_type = udev_device_get_format(info)
|
|
serial = udev_device_get_serial(info)
|
|
|
|
# Now, if the device is a disk, see if there is a usable disklabel.
|
|
# If not, see if the user would like to create one.
|
|
# XXX ignore disklabels on multipath or biosraid member disks
|
|
if not udev_device_is_biosraid(info) and \
|
|
not udev_device_is_multipath_member(info):
|
|
self.handleUdevDiskLabelFormat(info, device)
|
|
if device.partitioned or self.isIgnored(info) or \
|
|
(not device.partitionable and
|
|
device.format.type == "disklabel"):
|
|
# If the device has a disklabel, or the user chose not to
|
|
# create one, we are finished with this device. Otherwise
|
|
# it must have some non-disklabel formatting, in which case
|
|
# we fall through to handle that.
|
|
return
|
|
|
|
format = None
|
|
if (not device) or (not format_type) or device.format.type:
|
|
# this device has no formatting or it has already been set up
|
|
# FIXME: this probably needs something special for disklabels
|
|
log.debug("no type or existing type for %s, bailing" % (name,))
|
|
return
|
|
|
|
# set up the common arguments for the format constructor
|
|
args = [format_type]
|
|
kwargs = {"uuid": uuid,
|
|
"label": label,
|
|
"device": device.path,
|
|
"serial": serial,
|
|
"exists": True}
|
|
|
|
# set up type-specific arguments for the format constructor
|
|
if format_type == "multipath_member":
|
|
kwargs["multipath_members"] = self.getDevicesBySerial(serial)
|
|
elif format_type == "crypto_LUKS":
|
|
# luks/dmcrypt
|
|
kwargs["name"] = "luks-%s" % uuid
|
|
elif format_type in formats.mdraid.MDRaidMember._udevTypes:
|
|
# mdraid
|
|
try:
|
|
kwargs["mdUuid"] = udev_device_get_md_uuid(info)
|
|
except KeyError:
|
|
log.debug("mdraid member %s has no md uuid" % name)
|
|
elif format_type == "LVM2_member":
|
|
# lvm
|
|
try:
|
|
kwargs["vgName"] = udev_device_get_vg_name(info)
|
|
except KeyError as e:
|
|
log.debug("PV %s has no vg_name" % name)
|
|
try:
|
|
kwargs["vgUuid"] = udev_device_get_vg_uuid(info)
|
|
except KeyError:
|
|
log.debug("PV %s has no vg_uuid" % name)
|
|
try:
|
|
kwargs["peStart"] = udev_device_get_pv_pe_start(info)
|
|
except KeyError:
|
|
log.debug("PV %s has no pe_start" % name)
|
|
elif format_type == "vfat":
|
|
# efi magic
|
|
if isinstance(device, PartitionDevice) and device.bootable:
|
|
efi = formats.getFormat("efi")
|
|
if efi.minSize <= device.size <= efi.maxSize:
|
|
args[0] = "efi"
|
|
elif format_type == "hfs":
|
|
# apple bootstrap magic
|
|
if isinstance(device, PartitionDevice) and device.bootable:
|
|
apple = formats.getFormat("appleboot")
|
|
if apple.minSize <= device.size <= apple.maxSize:
|
|
args[0] = "appleboot"
|
|
|
|
try:
|
|
log.debug("type detected on '%s' is '%s'" % (name, format_type,))
|
|
device.format = formats.getFormat(*args, **kwargs)
|
|
except FSError:
|
|
log.debug("type '%s' on '%s' invalid, assuming no format" %
|
|
(format_type, name,))
|
|
device.format = formats.DeviceFormat()
|
|
return
|
|
|
|
if shouldClear(device, self.clearPartType,
|
|
clearPartDisks=self.clearPartDisks):
|
|
# if this is a device that will be cleared by clearpart,
|
|
# don't bother with format-specific processing
|
|
return
|
|
|
|
#
|
|
# now do any special handling required for the device's format
|
|
#
|
|
if device.format.type == "luks":
|
|
self.handleUdevLUKSFormat(info, device)
|
|
elif device.format.type == "mdmember":
|
|
self.handleUdevMDMemberFormat(info, device)
|
|
elif device.format.type == "dmraidmember":
|
|
self.handleUdevDMRaidMemberFormat(info, device)
|
|
elif device.format.type == "lvmpv":
|
|
self.handleUdevLVMPVFormat(info, device)
|
|
elif device.format.type == "multipath_member":
|
|
self.handleMultipathMemberFormat(info, device)
|
|
|
|
def _handleInconsistencies(self):
|
|
def reinitializeVG(vg):
|
|
# First we remove VG data
|
|
try:
|
|
vg.destroy()
|
|
except DeviceError:
|
|
# the pvremoves will finish the job.
|
|
log.debug("There was an error destroying the VG %s." % vg.name)
|
|
|
|
# remove VG device from list.
|
|
self._removeDevice(vg)
|
|
|
|
for parent in vg.parents:
|
|
parent.format.destroy()
|
|
|
|
# Give the vg the a default format
|
|
kwargs = {"device": parent.path,
|
|
"exists": parent.exists}
|
|
parent.format = formats.getFormat(*[""], **kwargs)
|
|
|
|
def leafInconsistencies(device):
|
|
if device.type == "lvmvg":
|
|
if device.complete:
|
|
return
|
|
|
|
paths = []
|
|
for parent in device.parents:
|
|
paths.append(parent.path)
|
|
|
|
# if zeroMbr is true don't ask.
|
|
if (self.zeroMbr or
|
|
self.intf.questionReinitInconsistentLVM(pv_names=paths,
|
|
vg_name=device.name)):
|
|
reinitializeVG(device)
|
|
else:
|
|
# The user chose not to reinitialize.
|
|
# hopefully this will ignore the vg components too.
|
|
self._removeDevice(device)
|
|
lvm.lvm_cc_addFilterRejectRegexp(device.name)
|
|
lvm.blacklistVG(device.name)
|
|
for parent in device.parents:
|
|
if parent.type == "partition":
|
|
self.immutableDevices.append([parent.name,
|
|
_("This partition is part of an inconsistent LVM Volume Group.")])
|
|
else:
|
|
self._removeDevice(parent, moddisk=False)
|
|
self.addIgnoredDisk(parent.name)
|
|
lvm.lvm_cc_addFilterRejectRegexp(parent.name)
|
|
|
|
elif device.type == "lvmlv":
|
|
# we might have already fixed this.
|
|
if device not in self._devices or \
|
|
device.name in self._ignoredDisks:
|
|
return
|
|
if device.complete:
|
|
return
|
|
|
|
paths = []
|
|
for parent in device.vg.parents:
|
|
paths.append(parent.path)
|
|
|
|
if (self.zeroMbr or
|
|
self.intf.questionReinitInconsistentLVM(pv_names=paths,
|
|
lv_name=device.name)):
|
|
|
|
# destroy all lvs.
|
|
for lv in device.vg.lvs:
|
|
try:
|
|
# reinitializeVG should clean up if necessary
|
|
lv.destroy()
|
|
except StorageError as e:
|
|
log.info("error removing lv %s from "
|
|
"inconsistent/incomplete vg %s"
|
|
% (lv.lvname, device.vg.name))
|
|
device.vg._removeLogVol(lv)
|
|
self._removeDevice(lv)
|
|
|
|
reinitializeVG(device.vg)
|
|
else:
|
|
# ignore all the lvs.
|
|
for lv in device.vg.lvs:
|
|
self._removeDevice(lv)
|
|
lvm.lvm_cc_addFilterRejectRegexp(lv.name)
|
|
# ignore the vg
|
|
self._removeDevice(device.vg)
|
|
lvm.lvm_cc_addFilterRejectRegexp(device.vg.name)
|
|
lvm.blacklistVG(device.vg.name)
|
|
# ignore all the pvs
|
|
for parent in device.vg.parents:
|
|
if parent.type == "partition":
|
|
self.immutableDevices.append([parent.name,
|
|
_("This partition is part of an inconsistent LVM Volume Group.")])
|
|
else:
|
|
self._removeDevice(parent, moddisk=False)
|
|
self.addIgnoredDisk(parent.name)
|
|
lvm.lvm_cc_addFilterRejectRegexp(parent.name)
|
|
|
|
# Address the inconsistencies present in the tree leaves.
|
|
for leaf in self.leaves:
|
|
leafInconsistencies(leaf)
|
|
|
|
# Check for unused BIOS raid members, unused dmraid members are added
|
|
# to self.unusedRaidMembers as they are processed, extend this list
|
|
# with unused mdraid BIOS raid members
|
|
for c in self.getDevicesByType("mdcontainer"):
|
|
if c.kids == 0:
|
|
self.unusedRaidMembers.extend(map(lambda m: m.name, c.devices))
|
|
|
|
self.intf.unusedRaidMembersWarning(self.unusedRaidMembers)
|
|
|
|
def populate(self):
|
|
""" Locate all storage devices. """
|
|
|
|
# mark the tree as unpopulated so exception handlers can tell the
|
|
# exception originated while finding storage devices
|
|
self.populated = False
|
|
|
|
# resolve the protected device specs to device names
|
|
for spec in self.protectedDevSpecs:
|
|
name = udev_resolve_devspec(spec)
|
|
if name:
|
|
self.protectedDevNames.append(name)
|
|
|
|
# FIXME: the backing dev for the live image can't be used as an
|
|
# install target. note that this is a little bit of a hack
|
|
# since we're assuming that /dev/live will exist
|
|
if os.path.exists("/dev/live") and \
|
|
stat.S_ISBLK(os.stat("/dev/live")[stat.ST_MODE]):
|
|
livetarget = devicePathToName(os.path.realpath("/dev/live"))
|
|
log.info("%s looks to be the live device; marking as protected"
|
|
% (livetarget,))
|
|
self.protectedDevNames.append(livetarget)
|
|
|
|
# First iteration - let's just look for disks.
|
|
old_devices = {}
|
|
|
|
devices = udev_get_block_devices()
|
|
for dev in devices:
|
|
old_devices[dev['name']] = dev
|
|
|
|
cfg = self.__multipathConfigWriter.write()
|
|
open("/etc/multipath.conf", "w+").write(cfg)
|
|
del cfg
|
|
|
|
(singles, mpaths, partitions) = devicelibs.mpath.identifyMultipaths(devices)
|
|
devices = singles + reduce(list.__add__, mpaths, []) + partitions
|
|
log.info("devices to scan: %s" % [d['name'] for d in devices])
|
|
for dev in devices:
|
|
self.addUdevDevice(dev)
|
|
|
|
# Having found all the disks, we can now find all the multipaths built
|
|
# upon them.
|
|
whitelist = []
|
|
mpaths = self.__multipaths.values()
|
|
mpaths.sort(key=lambda d: d.name)
|
|
for mp in mpaths:
|
|
log.info("adding mpath device %s" % mp.name)
|
|
mp.setup()
|
|
whitelist.append(mp.name)
|
|
for p in mp.parents:
|
|
whitelist.append(p.name)
|
|
self.__multipathConfigWriter.addMultipathDevice(mp)
|
|
self._addDevice(mp)
|
|
for d in self.devices:
|
|
if not d.name in whitelist:
|
|
self.__multipathConfigWriter.addBlacklistDevice(d)
|
|
cfg = self.__multipathConfigWriter.write()
|
|
open("/etc/multipath.conf", "w+").write(cfg)
|
|
del cfg
|
|
|
|
# Now, loop and scan for devices that have appeared since the two above
|
|
# blocks or since previous iterations.
|
|
while True:
|
|
devices = []
|
|
new_devices = udev_get_block_devices()
|
|
|
|
for new_device in new_devices:
|
|
if not old_devices.has_key(new_device['name']):
|
|
old_devices[new_device['name']] = new_device
|
|
devices.append(new_device)
|
|
|
|
if len(devices) == 0:
|
|
# nothing is changing -- we are finished building devices
|
|
break
|
|
|
|
log.info("devices to scan: %s" % [d['name'] for d in devices])
|
|
for dev in devices:
|
|
self.addUdevDevice(dev)
|
|
|
|
self.populated = True
|
|
|
|
# After having the complete tree we make sure that the system
|
|
# inconsistencies are ignored or resolved.
|
|
self._handleInconsistencies()
|
|
|
|
self.teardownAll()
|
|
try:
|
|
os.unlink("/etc/mdadm.conf")
|
|
except OSError:
|
|
log.info("failed to unlink /etc/mdadm.conf")
|
|
|
|
def teardownAll(self):
|
|
""" Run teardown methods on all devices. """
|
|
for device in self.leaves:
|
|
try:
|
|
device.teardown(recursive=True)
|
|
except StorageError as e:
|
|
log.info("teardown of %s failed: %s" % (device.name, e))
|
|
|
|
def setupAll(self):
|
|
""" Run setup methods on all devices. """
|
|
for device in self.leaves:
|
|
try:
|
|
device.setup()
|
|
except DeviceError as (msg, name):
|
|
log.debug("setup of %s failed: %s" % (device.name, msg))
|
|
|
|
def getDeviceBySysfsPath(self, path):
|
|
if not path:
|
|
return None
|
|
|
|
found = None
|
|
for device in self._devices:
|
|
if device.sysfsPath == path:
|
|
found = device
|
|
break
|
|
|
|
return found
|
|
|
|
def getDeviceByUuid(self, uuid):
|
|
if not uuid:
|
|
return None
|
|
|
|
found = None
|
|
for device in self._devices:
|
|
if device.uuid == uuid:
|
|
found = device
|
|
break
|
|
elif device.format.uuid == uuid:
|
|
found = device
|
|
break
|
|
|
|
return found
|
|
|
|
def getDevicesBySerial(self, serial):
|
|
devices = []
|
|
for device in self._devices:
|
|
if not hasattr(device, "serial"):
|
|
log.warning("device %s has no serial attr" % device.name)
|
|
continue
|
|
if device.serial == serial:
|
|
devices.append(device)
|
|
return devices
|
|
|
|
def getDeviceByLabel(self, label):
|
|
if not label:
|
|
return None
|
|
|
|
found = None
|
|
for device in self._devices:
|
|
_label = getattr(device.format, "label", None)
|
|
if not _label:
|
|
continue
|
|
|
|
if _label == label:
|
|
found = device
|
|
break
|
|
|
|
return found
|
|
|
|
def getDeviceByName(self, name):
|
|
log.debug("looking for device '%s'..." % name)
|
|
if not name:
|
|
return None
|
|
|
|
found = None
|
|
for device in self._devices:
|
|
if device.name == name:
|
|
found = device
|
|
break
|
|
elif (device.type == "lvmlv" or device.type == "lvmvg") and \
|
|
device.name == name.replace("--","-"):
|
|
found = device
|
|
break
|
|
|
|
log.debug("found %s" % found)
|
|
return found
|
|
|
|
def getDeviceByPath(self, path):
|
|
log.debug("looking for device '%s'..." % path)
|
|
if not path:
|
|
return None
|
|
|
|
found = None
|
|
for device in self._devices:
|
|
if device.path == path:
|
|
found = device
|
|
break
|
|
elif (device.type == "lvmlv" or device.type == "lvmvg") and \
|
|
device.path == path.replace("--","-"):
|
|
found = device
|
|
break
|
|
|
|
log.debug("found %s" % found)
|
|
return found
|
|
|
|
def getDevicesByType(self, device_type):
|
|
# TODO: expand this to catch device format types
|
|
return [d for d in self._devices if d.type == device_type]
|
|
|
|
def getDevicesByInstance(self, device_class):
|
|
return [d for d in self._devices if isinstance(d, device_class)]
|
|
|
|
@property
|
|
def devices(self):
|
|
""" List of device instances """
|
|
devices = []
|
|
for device in self._devices:
|
|
if device.path in [d.path for d in devices] and \
|
|
not isinstance(device, NoDevice):
|
|
raise DeviceTreeError("duplicate paths in device tree")
|
|
|
|
devices.append(device)
|
|
|
|
return devices
|
|
|
|
@property
|
|
def filesystems(self):
|
|
""" List of filesystems. """
|
|
#""" Dict with mountpoint keys and filesystem values. """
|
|
filesystems = []
|
|
for dev in self.leaves:
|
|
if dev.format and getattr(dev.format, 'mountpoint', None):
|
|
filesystems.append(dev.format)
|
|
|
|
return filesystems
|
|
|
|
@property
|
|
def uuids(self):
|
|
""" Dict with uuid keys and Device values. """
|
|
uuids = {}
|
|
for dev in self._devices:
|
|
try:
|
|
uuid = dev.uuid
|
|
except AttributeError:
|
|
uuid = None
|
|
|
|
if uuid:
|
|
uuids[uuid] = dev
|
|
|
|
try:
|
|
uuid = dev.format.uuid
|
|
except AttributeError:
|
|
uuid = None
|
|
|
|
if uuid:
|
|
uuids[uuid] = dev
|
|
|
|
return uuids
|
|
|
|
@property
|
|
def labels(self):
|
|
""" Dict with label keys and Device values.
|
|
|
|
FIXME: duplicate labels are a possibility
|
|
"""
|
|
labels = {}
|
|
for dev in self._devices:
|
|
if dev.format and getattr(dev.format, "label", None):
|
|
labels[dev.format.label] = dev
|
|
|
|
return labels
|
|
|
|
@property
|
|
def leaves(self):
|
|
""" List of all devices upon which no other devices exist. """
|
|
leaves = [d for d in self._devices if d.isleaf]
|
|
return leaves
|
|
|
|
def getChildren(self, device):
|
|
""" 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):
|
|
# find device in the tree
|
|
device = None
|
|
if devspec.startswith("UUID="):
|
|
# device-by-uuid
|
|
uuid = devspec.partition("=")[2]
|
|
device = self.uuids.get(uuid)
|
|
if device is None:
|
|
log.error("failed to resolve device %s" % devspec)
|
|
elif devspec.startswith("LABEL="):
|
|
# device-by-label
|
|
label = devspec.partition("=")[2]
|
|
device = self.labels.get(label)
|
|
if device is None:
|
|
log.error("failed to resolve device %s" % devspec)
|
|
elif devspec.startswith("/dev/"):
|
|
# device path
|
|
device = self.getDeviceByPath(devspec)
|
|
if device is None:
|
|
if blkidTab:
|
|
# try to use the blkid.tab to correlate the device
|
|
# path with a UUID
|
|
blkidTabEnt = blkidTab.get(devspec)
|
|
if blkidTabEnt:
|
|
log.debug("found blkid.tab entry for '%s'" % devspec)
|
|
uuid = blkidTabEnt.get("UUID")
|
|
if uuid:
|
|
device = self.getDeviceByUuid(uuid)
|
|
if device:
|
|
devstr = device.name
|
|
else:
|
|
devstr = "None"
|
|
log.debug("found device '%s' in tree" % devstr)
|
|
if device and device.format and \
|
|
device.format.type == "luks":
|
|
map_name = device.format.mapName
|
|
log.debug("luks device; map name is '%s'" % map_name)
|
|
mapped_dev = self.getDeviceByName(map_name)
|
|
if mapped_dev:
|
|
device = mapped_dev
|
|
|
|
if device is None and cryptTab and \
|
|
devspec.startswith("/dev/mapper/"):
|
|
# try to use a dm-crypt mapping name to
|
|
# obtain the underlying device, possibly
|
|
# using blkid.tab
|
|
cryptTabEnt = cryptTab.get(devspec.split("/")[-1])
|
|
if cryptTabEnt:
|
|
luks_dev = cryptTabEnt['device']
|
|
try:
|
|
device = self.getChildren(luks_dev)[0]
|
|
except IndexError as e:
|
|
pass
|
|
elif device is None:
|
|
# dear lvm: can we please have a few more device nodes
|
|
# for each logical volume?
|
|
# three just doesn't seem like enough.
|
|
name = devspec[5:] # strip off leading "/dev/"
|
|
(vg_name, slash, lv_name) = name.partition("/")
|
|
if lv_name and not "/" in lv_name:
|
|
# looks like we may have one
|
|
lv = "%s-%s" % (vg_name, lv_name)
|
|
device = self.getDeviceByName(lv)
|
|
|
|
if device:
|
|
log.debug("resolved '%s' to '%s' (%s)" % (devspec, device.name, device.type))
|
|
else:
|
|
log.debug("failed to resolve '%s'" % devspec)
|
|
return device
|