330 lines
11 KiB
Python
330 lines
11 KiB
Python
|
#
|
||
|
# Kickstart specification for the storage.
|
||
|
#
|
||
|
# Copyright (C) 2018 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.
|
||
|
#
|
||
|
from blivet.fcoe import fcoe
|
||
|
from blivet.iscsi import iscsi
|
||
|
from blivet.zfcp import zfcp
|
||
|
from blivet.formats import get_format
|
||
|
from blivet.formats.disklabel import DiskLabel
|
||
|
from pykickstart.constants import CLEARPART_TYPE_NONE
|
||
|
from pykickstart.errors import KickstartParseError
|
||
|
|
||
|
from pyanaconda.network import get_supported_devices, wait_for_network_devices
|
||
|
from pyanaconda.modules.common.constants.services import NETWORK
|
||
|
from pyanaconda.core.constants import FIPS_PASSPHRASE_MIN_LENGTH
|
||
|
from pyanaconda.core.i18n import _
|
||
|
from pyanaconda.core.kernel import kernel_arguments
|
||
|
from pyanaconda.core.kickstart import KickstartSpecification, commands as COMMANDS
|
||
|
from pyanaconda.core.storage import device_matches
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
__all__ = ["StorageKickstartSpecification"]
|
||
|
|
||
|
|
||
|
def get_device_names(specs, disks_only=False, msg="{}", lineno=None):
|
||
|
"""Get device names from device specifications."""
|
||
|
drives = []
|
||
|
|
||
|
for spec in specs:
|
||
|
matched = device_matches(spec, disks_only=disks_only)
|
||
|
if not matched:
|
||
|
raise KickstartParseError(msg.format(spec), lineno=lineno)
|
||
|
else:
|
||
|
drives.extend(matched)
|
||
|
|
||
|
return drives
|
||
|
|
||
|
|
||
|
def fips_check_luks_passphrase(luks_passphrase, command_name, line_number):
|
||
|
"""Is the LUKS passphrase long enough in FIPS mode?
|
||
|
|
||
|
Signal a parse error if not.
|
||
|
|
||
|
This function is meant to be called indiscriminately, it will determine itself if FIPS is on.
|
||
|
|
||
|
:param str luks_passphrase: LUKS passphrase to check
|
||
|
:param str command_name: name of the command that had the passphrase
|
||
|
:param int line_number: line number where the command is found
|
||
|
:raise KickstartParseError: When the passphrase is not long enough
|
||
|
"""
|
||
|
if not luks_passphrase:
|
||
|
return
|
||
|
|
||
|
if not kernel_arguments.is_enabled("fips"):
|
||
|
return
|
||
|
|
||
|
if len(luks_passphrase) >= FIPS_PASSPHRASE_MIN_LENGTH:
|
||
|
return
|
||
|
|
||
|
raise KickstartParseError(
|
||
|
_("Passphrase given in the {} command is too short in FIPS mode. "
|
||
|
"Please use at least {} characters.").format(command_name, FIPS_PASSPHRASE_MIN_LENGTH),
|
||
|
lineno=line_number
|
||
|
)
|
||
|
|
||
|
|
||
|
class AutoPart(COMMANDS.AutoPart):
|
||
|
"""The autopart kickstart command."""
|
||
|
|
||
|
def parse(self, args):
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
if self.fstype:
|
||
|
fmt = get_format(self.fstype)
|
||
|
|
||
|
if not fmt or fmt.type is None:
|
||
|
raise KickstartParseError(_("File system type \"{}\" given in autopart command is "
|
||
|
"invalid.").format(self.fstype), lineno=self.lineno)
|
||
|
|
||
|
fips_check_luks_passphrase(self.passphrase, "autopart", self.lineno)
|
||
|
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class ClearPart(COMMANDS.ClearPart):
|
||
|
"""The clearpart kickstart command."""
|
||
|
|
||
|
def parse(self, args):
|
||
|
"""Parse the command.
|
||
|
|
||
|
Do any glob expansion now, since we need to have the real
|
||
|
list of disks available before the execute methods run.
|
||
|
"""
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
# Set the default type.
|
||
|
if self.type is None:
|
||
|
self.type = CLEARPART_TYPE_NONE
|
||
|
|
||
|
# Check the disk label.
|
||
|
if self.disklabel and self.disklabel not in DiskLabel.get_platform_label_types():
|
||
|
raise KickstartParseError(_("Disklabel \"{}\" given in clearpart command is not "
|
||
|
"supported on this platform.").format(self.disklabel),
|
||
|
lineno=self.lineno)
|
||
|
|
||
|
# Get the disks names to clear.
|
||
|
self.drives = get_device_names(self.drives, disks_only=True, lineno=self.lineno,
|
||
|
msg=_("Disk \"{}\" given in clearpart command does "
|
||
|
"not exist."))
|
||
|
|
||
|
# Get the devices names to clear.
|
||
|
self.devices = get_device_names(self.devices, disks_only=False, lineno=self.lineno,
|
||
|
msg=_("Device \"{}\" given in clearpart device list "
|
||
|
"does not exist."))
|
||
|
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class IgnoreDisk(COMMANDS.IgnoreDisk):
|
||
|
"""The ignoredisk kickstart command."""
|
||
|
|
||
|
def parse(self, args):
|
||
|
"""Parse the command.
|
||
|
|
||
|
Do any glob expansion now, since we need to have the real
|
||
|
list of disks available before the execute methods run.
|
||
|
"""
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
# Get the ignored disk names.
|
||
|
self.ignoredisk = get_device_names(self.ignoredisk, disks_only=True, lineno=self.lineno,
|
||
|
msg=_("Disk \"{}\" given in ignoredisk command does "
|
||
|
"not exist."))
|
||
|
|
||
|
# Get the selected disk names.
|
||
|
self.onlyuse = get_device_names(self.onlyuse, disks_only=True, lineno=self.lineno,
|
||
|
msg=_("Disk \"{}\" given in ignoredisk command does "
|
||
|
"not exist."))
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class Fcoe(COMMANDS.Fcoe):
|
||
|
def parse(self, args):
|
||
|
fc = super().parse(args)
|
||
|
|
||
|
if fc.nic not in [dev.device_name for dev in get_supported_devices()]:
|
||
|
raise KickstartParseError(_("NIC \"{}\" given in fcoe command does not "
|
||
|
"exist.").format(fc.nic), lineno=self.lineno)
|
||
|
|
||
|
if fc.nic in (info[0] for info in fcoe.nics):
|
||
|
log.info("Kickstart fcoe device %s was already added from EDD, ignoring.", fc.nic)
|
||
|
else:
|
||
|
msg = fcoe.add_san(nic=fc.nic, dcb=fc.dcb, auto_vlan=True)
|
||
|
|
||
|
if not msg:
|
||
|
msg = "Succeeded."
|
||
|
fcoe.added_nics.append(fc.nic)
|
||
|
|
||
|
log.info("Adding FCoE SAN on %s: %s", fc.nic, msg)
|
||
|
|
||
|
return fc
|
||
|
|
||
|
|
||
|
class Iscsi(COMMANDS.Iscsi):
|
||
|
def parse(self, args):
|
||
|
tg = super().parse(args)
|
||
|
|
||
|
if tg.iface:
|
||
|
if not wait_for_network_devices([tg.iface]):
|
||
|
raise KickstartParseError(
|
||
|
lineno=self.lineno,
|
||
|
msg=_("Network interface \"{nic}\" required by iSCSI \"{iscsi_target}\" "
|
||
|
"target is not up.").format(
|
||
|
nic=tg.iface,
|
||
|
iscsi_target=tg.target
|
||
|
)
|
||
|
)
|
||
|
|
||
|
mode = iscsi.mode
|
||
|
if mode == "none":
|
||
|
if tg.iface:
|
||
|
network_proxy = NETWORK.get_proxy()
|
||
|
activated_ifaces = network_proxy.GetActivatedInterfaces()
|
||
|
iscsi.create_interfaces(activated_ifaces)
|
||
|
elif ((mode == "bind" and not tg.iface) or (mode == "default" and tg.iface)):
|
||
|
raise KickstartParseError(
|
||
|
lineno=self.lineno,
|
||
|
msg=_("iscsi --iface must be specified (binding used) either for all targets "
|
||
|
"or for none")
|
||
|
)
|
||
|
|
||
|
try:
|
||
|
if tg.target:
|
||
|
log.info("adding iscsi target %s at %s:%d via %s",
|
||
|
tg.target, tg.ipaddr, tg.port, tg.iface)
|
||
|
else:
|
||
|
log.info("adding all iscsi targets discovered at %s:%d via %s",
|
||
|
tg.ipaddr, tg.port, tg.iface)
|
||
|
iscsi.add_target(tg.ipaddr, tg.port, tg.user,
|
||
|
tg.password, tg.user_in,
|
||
|
tg.password_in,
|
||
|
target=tg.target,
|
||
|
iface=tg.iface)
|
||
|
except (IOError, ValueError) as e:
|
||
|
raise KickstartParseError(lineno=self.lineno, msg=str(e)) from e
|
||
|
|
||
|
return tg
|
||
|
|
||
|
|
||
|
class IscsiName(COMMANDS.IscsiName):
|
||
|
def parse(self, args):
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
iscsi.initiator = self.iscsiname
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class LogVol(COMMANDS.LogVol):
|
||
|
def parse(self, args):
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
fips_check_luks_passphrase(retval.passphrase, "logvol", self.lineno)
|
||
|
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class Partition(COMMANDS.Partition):
|
||
|
def parse(self, args):
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
fips_check_luks_passphrase(retval.passphrase, self.currentCmd, self.lineno)
|
||
|
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class Raid(COMMANDS.Raid):
|
||
|
def parse(self, args):
|
||
|
retval = super().parse(args)
|
||
|
|
||
|
fips_check_luks_passphrase(retval.passphrase, "raid", self.lineno)
|
||
|
|
||
|
return retval
|
||
|
|
||
|
|
||
|
class Snapshot(COMMANDS.Snapshot):
|
||
|
"""The snapshot kickstart command."""
|
||
|
|
||
|
def parse(self, args):
|
||
|
request = super().parse(args)
|
||
|
|
||
|
if not request.origin.count('/') == 1:
|
||
|
raise KickstartParseError(_("Incorrectly specified origin of the snapshot. Use "
|
||
|
"format \"VolGroup/LV-name\""), lineno=request.lineno)
|
||
|
|
||
|
return request
|
||
|
|
||
|
|
||
|
class ZFCP(COMMANDS.ZFCP):
|
||
|
"""The zfcp kickstart command."""
|
||
|
|
||
|
def parse(self, args):
|
||
|
fcp = super().parse(args)
|
||
|
|
||
|
# We need to bring the device online before we check
|
||
|
# device names in other commands. See commit: 4e038ca
|
||
|
try:
|
||
|
zfcp.add_fcp(fcp.devnum, fcp.wwpn, fcp.fcplun)
|
||
|
except ValueError as e:
|
||
|
log.warning(str(e))
|
||
|
|
||
|
return fcp
|
||
|
|
||
|
|
||
|
class StorageKickstartSpecification(KickstartSpecification):
|
||
|
"""Kickstart specification of the storage module."""
|
||
|
|
||
|
commands = {
|
||
|
"autopart": AutoPart,
|
||
|
"bootloader": COMMANDS.Bootloader,
|
||
|
"btrfs": COMMANDS.BTRFS,
|
||
|
"clearpart": ClearPart,
|
||
|
"fcoe": Fcoe,
|
||
|
"ignoredisk": IgnoreDisk,
|
||
|
"iscsi": Iscsi,
|
||
|
"iscsiname": IscsiName,
|
||
|
"logvol": LogVol,
|
||
|
"mount": COMMANDS.Mount,
|
||
|
"nvdimm": COMMANDS.Nvdimm,
|
||
|
"part": Partition,
|
||
|
"partition": Partition,
|
||
|
"raid": Raid,
|
||
|
"reqpart": COMMANDS.ReqPart,
|
||
|
"snapshot": Snapshot,
|
||
|
"volgroup": COMMANDS.VolGroup,
|
||
|
"zerombr": COMMANDS.ZeroMbr,
|
||
|
"zfcp": ZFCP,
|
||
|
"zipl": COMMANDS.Zipl
|
||
|
}
|
||
|
|
||
|
commands_data = {
|
||
|
"BTRFSData": COMMANDS.BTRFSData,
|
||
|
"FcoeData": COMMANDS.FcoeData,
|
||
|
"IscsiData": COMMANDS.IscsiData,
|
||
|
"LogVolData": COMMANDS.LogVolData,
|
||
|
"MountData": COMMANDS.MountData,
|
||
|
"NvdimmData": COMMANDS.NvdimmData,
|
||
|
"PartData": COMMANDS.PartData,
|
||
|
"RaidData": COMMANDS.RaidData,
|
||
|
"SnapshotData": COMMANDS.SnapshotData,
|
||
|
"VolGroupData": COMMANDS.VolGroupData,
|
||
|
"ZFCPData": COMMANDS.ZFCPData,
|
||
|
}
|