avocado-framework/avocado

View on GitHub
avocado/utils/softwareraid.py

Summary

Maintainability
A
1 hr
Test Coverage
# 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 LICENSE for more details.
#
# Copyright: 2020 IBM
# Author: Narasimhan V <sim@linux.vnet.ibm.com>
# Author: Venkat Rao B <vrbagal1@linux.vnet.ibm.com>

"""
This module provides APIs to work with software raid.
"""


import logging
import time

from avocado.utils import process

LOG = logging.getLogger(__name__)


class SoftwareRaid:
    """
    Perform software raid related operations.

    :param name: Name of the software raid to be created
    :type name: str
    :param level: Level of software raid to be created
    :type name: str
    :param disks: List of disks for software raid
    :type disks: list
    :param metadata: Metadata level for software raid
    :type metadata: str
    :param spare_disks: List of spare disks for software raid
    :type spare_disks: list
    """

    def __init__(self, name, level, disks, metadata, spare_disks=None):
        self.name = name
        self.level = level
        self.metadata = metadata
        self.disks = disks
        self.remadd = ""
        self.spare_disks = spare_disks or []

    def _run_command(self, cmd, log_details=True, check_recovery=False):
        if process.system(cmd, ignore_status=True, shell=True) != 0:
            if log_details:
                self.get_detail()
            return False
        if check_recovery:
            while self.is_recovering():
                time.sleep(30)
        if log_details:
            self.get_detail()
        return True

    def add_disk(self, disk):
        """
        Adds disk specified to software raid.

        :param disk: disk to be added.
        :type disk: str

        :return: True if add is successful, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm {self.name} --add {disk}"
        return self._run_command(cmd)

    def assemble(self):
        """
        Assembles software raid.

        :return: True if assembled, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm --assemble {self.name} {' '.join(self.disks)}"
        if self.spare_disks:
            cmd += f" {' '.join(self.spare_disks)}"
        return self._run_command(cmd, check_recovery=True)

    def clear_superblock(self):
        """
        Zeroes superblocks in member devices of raid.

        :return: True if zeroed, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm --zero-superblock {' '.join(self.disks)}"
        if self.spare_disks:
            cmd += f" {' '.join(self.spare_disks)}"
        return self._run_command(cmd, log_details=False)

    def create(self):
        """
        Creates software raid.

        :return: True if raid is created. False otherwise.
        :rtype: bool
        """
        cmd = f"yes | mdadm --create --assume-clean {self.name}"
        cmd += f" --level={self.level}"
        cmd += f" --raid-devices={len(self.disks)} {' '.join(self.disks)}"
        cmd += f" --metadata={self.metadata}"
        if self.spare_disks:
            cmd += (
                f" --spare-devices={len(self.spare_disks)} "
                f"{' '.join(self.spare_disks)}"
            )
        cmd += " --verbose --force"
        return self._run_command(cmd)

    def get_detail(self):
        """
        Returns mdadm details.

        :return: mdadm --detail output
        :rtype: str
        """
        cmd = f"mdadm --detail {self.name}"
        output = process.run(cmd, ignore_status=True, shell=True)
        return output.stdout_text

    def is_recovering(self):
        """
        Checks if raid is recovering.

        :return: True if recovering, False otherwise.
        :rtype: bool
        """
        LOG.debug("RECOVERY")
        for line in self.get_detail().splitlines():
            if "State" in line and "recovering" in line:
                return True
        return False

    def remove_disk(self, disk):
        """
        Removes disk specified from software raid.

        :param disk: disk to be removed.
        :type disk: str

        :return: True if remove is successful, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm {self.name} --fail {disk} --remove {disk}"
        return self._run_command(cmd)

    def stop(self):
        """
        Stops software raid.

        :return: True if stopped, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm --manage {self.name} --stop"
        return self._run_command(cmd, log_details=False)

    def exists(self):
        """
        checks if softwareraid exists or not

        :mdadm: must be super-user(root) to perform this action

        :return: True if exists, False otherwise.
        :rtype: bool
        """
        cmd = f"mdadm --detail --test {self.name}"
        result = process.run(cmd, shell=True, sudo=True, ignore_status=True)
        if result.exit_status == 4:
            return False
        return True