Source code for insights.parsers.mdstat

"""
Mdstat - file ``/proc/mdstat``
==============================
"""
import re

from insights.core import CommandParser
from insights.core.exceptions import ParseException, SkipComponent
from insights.core.plugins import parser
from insights.specs import Specs


[docs] @parser(Specs.mdstat) class Mdstat(CommandParser): """ Represents the information in the ``/proc/mdstat`` file. Several examples of possible data containe in the file can be found on the `MDstat kernel.org wiki page <https://raid.wiki.kernel.org/index.php/Mdstat>`_. In particular, the discussion here will focus on initial extraction of information form lines such as:: Personalities : [raid1] [raid6] [raid5] [raid4] md1 : active raid1 sdb2[1] sda2[0] 136448 blocks [2/2] [UU] md2 : active raid1 sdb3[1] sda3[0] 129596288 blocks [2/2] [UU] md3 : active raid5 sdl1[9] sdk1[8] sdj1[7] sdi1[6] sdh1[5] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0] 1318680576 blocks level 5, 1024k chunk, algorithm 2 [10/10] [UUUUUUUUUU] unused devices: <none> The data contained in ``mdstat`` is represented with three top level members - ``personalities``, ``components`` and ``mds``. Attributes: personalities (list): A list of RAID levels the kernel currently supports. components (list): A list containing a dict of md component device information. Each of these dicts contains the following keys - ``device_name`` : string - name of the array device - ``active`` : boolean - ``True`` if the array is active, ``False`` if it is inactive. - ``component_name`` : string - name of the component device - ``raid`` : string - with the raid level, e.g., "raid1" for "md1" - ``role`` : int - raid role number - ``device_flag`` : str - device component status flag. Known values include 'F' (failed device), 'S', and 'W' - ``up`` : boolean - ``True`` if the component device is up - ``auto_read_only`` : boolean - ``True`` if the array device is "auto-read-only" - ``blocks`` : the number of blocks in the device - ``level`` : the current RAID level, if found in the status line - ``chunk`` : the device chunk size, if found in the status line - ``algorithm`` : the current conflict resolution algorithm, if found in the status line mds (dict): A dictionary keyed on the MD device name. Each dict contains the following keys - ``name``: Name of the MD device - ``active``: Whether the MD device is active - ``raid``: The RAID type string - ``devices``: a list of the devices in this - ``blocks``, ``level``, ``chunk`` and ``algorithm`` - the same information given above per component device (if found) Examples: >>> type(mdstat) <class 'insights.parsers.mdstat.Mdstat'> >>> mdstat.personalities ['raid1', 'raid6', 'raid5', 'raid4'] >>> len(mdstat.components) 14 >>> mdstat.components[0]['device_name'] 'md1' >>> sdb2 = mdstat.components[0] >>> sdb2['component_name'] 'sdb2' >>> sdb2['active'] True >>> sdb2['raid'] 'raid1' >>> sdb2['role'] 1 >>> sdb2['up'] True >>> sorted(mdstat.mds.keys()) ['md1', 'md2', 'md3'] >>> mdstat.mds['md1']['active'] True >>> len(mdstat.mds['md1']['devices']) 2 >>> mdstat.mds['md1']['devices'][0]['component_name'] 'sdb2' """
[docs] def parse_content(self, content): if not content: raise SkipComponent("Empty output.") self.mds = {} self.components = [] self.personalities = [] current_components = None in_component = False for line in content: line = line.strip() if line.startswith('Personalities'): # If the line doesn't have any raid types then md raid isn't active. if line == "Personalities :": raise SkipComponent("No parseable md devices present.") in_component = False self.personalities = parse_personalities(line) # Starting a component array stanza. elif line.startswith("md"): in_component = True current_components = parse_array_start(line) # Catch any blank lines, this signals # the end of the component array stanza. elif not line: if in_component: self.components.extend(current_components) current_components = None in_component = False else: if in_component: parse_array_status(line, current_components) upstring = parse_upstring(line) if upstring: apply_upstring(upstring, current_components) # Map component devices into MDs dictionary by device name. for comp in self.components: devname = comp['device_name'] if devname not in self.mds: # md2 : active raid1 sdb3[1] sda3[0] # 129596288 blocks [2/2] [UU] self.mds[devname] = { 'name': devname, 'active': comp['active'], 'raid': comp['raid'], 'blocks': comp['blocks'], 'devices': [] } for opt in ['level', 'chunk', 'algorithm']: if opt in comp: self.mds[devname][opt] = comp[opt] self.mds[devname]['devices'].append(dict( (k, comp[k]) for k in comp if k in ['component_name', 'role', 'up'] )) # Keep self.data just for backwards compatibility. self.data = { 'personalities': self.personalities, 'components': self.components }
[docs] def parse_personalities(personalities_line): """ Parse the "personalities" line of ``/proc/mdstat``. Sample of personalities_line:: Personalities : [linear] [raid0] [raid1] [raid5] [raid4] [raid6] Args: personalities_line (str): A single "Personalities" line from an ``/proc/mdstat`` files. Returns: list: A list of raid "personalities" listed on the line. Raises: ParseException: If the format isn't like the sample above. """ personalities = [] if "Personalities :" not in personalities_line: raise ParseException("Incorrectly formatted personalities line.") tokens = personalities_line.split() for token in tokens[2:]: if token.startswith('[') and token.endswith(']'): personalities.append(token.strip('[]')) else: raise ParseException("Incorrectly formatted personalities line.") return personalities
[docs] def parse_array_start(md_line): """ Parse the initial line of a device array stanza in ``/proc/mdstat``. Sample of md_line:: md2 : active raid1 sdb3[1] sda3[0] Args: md_line (str): A single line from the start of a device array stanza. Returns: list: A list of dictionaries, one dictionary for each component device making up the array. Raises: ParseException: If the format isn't like the sample above. """ auto_read_only = False components = [] device_flag = None raid = None # Split the line to create tokens, and # set device_name to the first token. tokens = md_line.split() device_name = tokens.pop(0) if not device_name.startswith("md") or ":" not in tokens: raise ParseException("The md line isn't as expected.") # Remove the : symbol. tokens.pop(0) active_string = tokens.pop(0) if active_string == "active": active = True elif active_string == "inactive": active = False else: raise ParseException("The raid isn't marked as active or inactive.") # Only active raids have the auto-read-only # entry or the raid level. if active: raid_read_only_token = tokens.pop(0) if raid_read_only_token == "(auto-read-only)": auto_read_only = True raid = tokens.pop(0) else: raid = raid_read_only_token for token in tokens: # Token should be sda1[0] or sda1[0](S) for # example, and subtokens should be # ['sda1', '0'] or ['sda1', '0', '(S)']. subtokens = re.split(r"[\[\]]", token) if len(subtokens) <= 1: raise ParseException("The len of subtokens '{s_tokens}' is incorrect.".format(s_tokens=subtokens)) comp_name = subtokens[0] role = int(subtokens[1]) if len(subtokens) > 2: device_flag = subtokens[2].strip('()') components.append({ "device_name": device_name, "raid": raid, "active": active, "auto_read_only": auto_read_only, "component_name": comp_name, "role": role, "device_flag": device_flag }) return components
[docs] def parse_array_status(line, components): """ Parse the array status line, e.g.:: 1318680576 blocks level 5, 1024k chunk, algorithm 2 [10/10] [UUUUUUUUUU] This retrieves the following pieces of information: * ``blocks`` - (int) number of blocks in the whole MD device (always present) * ``level`` - (int) if found, the present RAID level * ``chunksize`` - (str) if found, the size of the data chunk in kilobytes * ``algorithm`` - (int) if found, the current algorithm in use. Because of the way data is stored per-component and not per-array, this then puts the above keys into each of the component dictionaries in the list we've been given. Sample data:: 1250241792 blocks super 1.2 level 5, 64k chunk, algorithm 2 [5/5] [UUUUUU] 1465151808 blocks level 5, 64k chunk, algorithm 2 [4/3] [UUU_] 136448 blocks [2/2] [UU] 6306 blocks super external:imsm<Paste> Args: line (str): The array status line to parse. components (list): A list of component dicts. """ status_line_re = r'(?P<blocks>\d+) blocks' + \ r'(?: super (?P<super>\S+))?' + \ r'(?: level (?P<level>\d+),)?' + \ r'(?: (?P<chunk>\d+k) chunk,)?' + \ r'(?: algorithm (?P<algorithm>\d+))?' # Since we're only called once per line, unless there's a good way to # cache this regular expression in compiled form we're going to have to # compile it each time. status_line_rex = re.compile(status_line_re) match = status_line_rex.search(line) if match: attributes = {'blocks': int(match.group('blocks'))} if match.group('level'): attributes['level'] = int(match.group('level')) if match.group('chunk'): attributes['chunk'] = match.group('chunk') if match.group('algorithm'): attributes['algorithm'] = int(match.group('algorithm')) for comp in components: comp.update(attributes)
[docs] def parse_upstring(line): """ Parse the subsequent lines of a device array stanza in ``/proc/mdstat`` for the "up" indicator string. The up indicator is "U" and down indicator is "_". Samples of line:: 129596288 blocks [2/2] [UU] 1318680576 blocks level 5, 1024k chunk, algorithm 2 [10/10] [UUU_UUUUUU] Parameters line (str): A single line from a device array stanza. Returns: str: The string containing a series of ``U`` and ``_`` characters if found in the string, and ``None`` if the up string is not found. """ UP_STRING_REGEX = r"\[(U|_)+]" match = re.search(UP_STRING_REGEX, line) if match: return match.group().strip('[]') else: return None
[docs] def apply_upstring(upstring, component_list): """Update the dictionaries resulting from ``parse_array_start`` with the "up" key based on the upstring returned from ``parse_upstring``. The function assumes that the upstring and component_list parameters passed in are from the same device array stanza of a ``/proc/mdstat`` file. The function modifies component_list in place, adding or updating the value of the "up" key to True if there is a corresponding ``U`` in the upstring string, or to False if there is a corresponding ``_``. If there the number of rows in component_list does not match the number of characters in upstring, an ``AssertionError`` is raised. Args: upstring (str): String sequence of ``U``s and ``_``s as determined by the ``parse_upstring`` method. component_list (list): List of dictionaries output from the ``parse_array_start`` method. """ def add_up_key(comp, up_char): if up_char not in ['U', '_']: raise ParseException("Invalid character for up_indicator '{word}'.".format(word=up_char)) comp['up'] = up_char == 'U' if len(upstring) != len(component_list): raise ParseException("Length of upstring and component_list doesn't match.") for comp_dict, up_indicator in zip(component_list, upstring): add_up_key(comp_dict, up_indicator)