PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/disk.py |
""" Module for managing disks and blockdevices """ import collections import decimal import logging import os import re import subprocess import salt.utils.decorators import salt.utils.decorators.path import salt.utils.path import salt.utils.platform from salt.exceptions import CommandExecutionError __func_alias__ = {"format_": "format"} log = logging.getLogger(__name__) def __virtual__(): """ Only work on POSIX-like systems """ if salt.utils.platform.is_windows(): return False, "This module doesn't work on Windows." return True def _parse_numbers(text): """ Convert a string to a number, allowing for a K|M|G|T postfix, 32.8K. Returns a decimal number if the string is a real number, or the string unchanged otherwise. """ if text.isdigit(): return decimal.Decimal(text) try: postPrefixes = { "K": "10E3", "M": "10E6", "G": "10E9", "T": "10E12", "P": "10E15", "E": "10E18", "Z": "10E21", "Y": "10E24", } if text[-1] in postPrefixes: v = decimal.Decimal(text[:-1]) v = v * decimal.Decimal(postPrefixes[text[-1]]) return v else: return decimal.Decimal(text) except ValueError: return text def _clean_flags(args, caller): """ Sanitize flags passed into df """ flags = "" if args is None: return flags # TODO: most of these cause the result parsing to fail allowed = ("a", "B", "h", "H", "i", "k", "l", "P", "t", "T", "x", "v") for flag in args: if flag in allowed: flags += flag else: raise CommandExecutionError(f"Invalid flag passed to {caller}") return flags def usage(args=None): """ Return usage information for volumes mounted on this minion args Sequence of flags to pass to the ``df`` command. .. versionchanged:: 2019.2.0 Default for SunOS changed to 1 kilobyte blocks CLI Example: .. code-block:: bash salt '*' disk.usage """ flags = _clean_flags(args, "disk.usage") if not os.path.isfile("/etc/mtab") and __grains__["kernel"] == "Linux": log.error("df cannot run without /etc/mtab") if __grains__.get("virtual_subtype") == "LXC": log.error( "df command failed and LXC detected. If you are running " "a Docker container, consider linking /proc/mounts to " "/etc/mtab or consider running Docker with -privileged" ) return {} if __grains__["kernel"] == "Linux": cmd = "df -P" elif __grains__["kernel"] == "OpenBSD" or __grains__["kernel"] == "AIX": cmd = "df -kP" elif __grains__["kernel"] == "SunOS": cmd = "df -k" else: cmd = "df" if flags: cmd += f" -{flags}" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() oldline = None for line in out: if not line: continue if line.startswith("Filesystem"): continue if oldline: line = oldline + " " + line comps = line.split() if len(comps) == 1: oldline = line continue else: oldline = None while len(comps) >= 2 and not comps[1].isdigit(): comps[0] = f"{comps[0]} {comps[1]}" comps.pop(1) if len(comps) < 2: continue try: if __grains__["kernel"] == "Darwin": ret[comps[8]] = { "filesystem": comps[0], "512-blocks": comps[1], "used": comps[2], "available": comps[3], "capacity": comps[4], "iused": comps[5], "ifree": comps[6], "%iused": comps[7], } else: ret[comps[5]] = { "filesystem": comps[0], "1K-blocks": comps[1], "used": comps[2], "available": comps[3], "capacity": comps[4], } except IndexError: log.error("Problem parsing disk usage information") ret = {} return ret def inodeusage(args=None): """ Return inode usage information for volumes mounted on this minion args Sequence of flags to pass to the ``df`` command. CLI Example: .. code-block:: bash salt '*' disk.inodeusage """ flags = _clean_flags(args, "disk.inodeusage") if __grains__["kernel"] == "AIX": cmd = "df -i" else: cmd = "df -iP" if flags: cmd += f" -{flags}" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() for line in out: if line.startswith("Filesystem"): continue comps = line.split() # Don't choke on empty lines if not comps: continue try: if __grains__["kernel"] == "OpenBSD": ret[comps[8]] = { "inodes": int(comps[5]) + int(comps[6]), "used": comps[5], "free": comps[6], "use": comps[7], "filesystem": comps[0], } elif __grains__["kernel"] == "AIX": ret[comps[6]] = { "inodes": comps[4], "used": comps[5], "free": comps[2], "use": comps[5], "filesystem": comps[0], } else: ret[comps[5]] = { "inodes": comps[1], "used": comps[2], "free": comps[3], "use": comps[4], "filesystem": comps[0], } except (IndexError, ValueError): log.error("Problem parsing inode usage information") ret = {} return ret def percent(args=None): """ Return partition information for volumes mounted on this minion args Specify a single partition for which to return data. CLI Example: .. code-block:: bash salt '*' disk.percent /var """ if __grains__["kernel"] == "Linux": cmd = "df -P" elif __grains__["kernel"] == "OpenBSD" or __grains__["kernel"] == "AIX": cmd = "df -kP" else: cmd = "df" ret = {} out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() for line in out: if not line: continue if line.startswith("Filesystem"): continue comps = line.split() while len(comps) >= 2 and not comps[1].isdigit(): comps[0] = f"{comps[0]} {comps[1]}" comps.pop(1) if len(comps) < 2: continue try: if __grains__["kernel"] == "Darwin": ret[comps[8]] = comps[4] else: ret[comps[5]] = comps[4] except IndexError: log.error("Problem parsing disk usage information") ret = {} if args and args not in ret: log.error( "Problem parsing disk usage information: Partition '%s' does not exist!", args, ) ret = {} elif args: return ret[args] return ret @salt.utils.decorators.path.which("blkid") def blkid(device=None, token=None): """ Return block device attributes: UUID, LABEL, etc. This function only works on systems where blkid is available. device Device name from the system token Any valid token used for the search CLI Example: .. code-block:: bash salt '*' disk.blkid salt '*' disk.blkid /dev/sda salt '*' disk.blkid token='UUID=6a38ee5-7235-44e7-8b22-816a403bad5d' salt '*' disk.blkid token='TYPE=ext4' """ cmd = ["blkid"] if device: cmd.append(device) elif token: cmd.extend(["-t", token]) ret = {} blkid_result = __salt__["cmd.run_all"](cmd, python_shell=False) if blkid_result["retcode"] > 0: return ret for line in blkid_result["stdout"].splitlines(): if not line: continue comps = line.split() device = comps[0][:-1] info = {} device_attributes = re.split('"*"', line.partition(" ")[2]) for key, value in zip(*[iter(device_attributes)] * 2): key = key.strip("=").strip(" ") info[key] = value.strip('"') ret[device] = info return ret def tune(device, **kwargs): """ Set attributes for the specified device CLI Example: .. code-block:: bash salt '*' disk.tune /dev/sda1 read-ahead=1024 read-write=True Valid options are: ``read-ahead``, ``filesystem-read-ahead``, ``read-only``, ``read-write``. See the ``blockdev(8)`` manpage for a more complete description of these options. """ kwarg_map = { "read-ahead": "setra", "filesystem-read-ahead": "setfra", "read-only": "setro", "read-write": "setrw", } opts = "" args = [] for key in kwargs: if key in kwarg_map: switch = kwarg_map[key] if key != "read-write": args.append(switch.replace("set", "get")) else: args.append("getro") if kwargs[key] == "True" or kwargs[key] is True: opts += f"--{key} " else: opts += f"--{switch} {kwargs[key]} " cmd = f"blockdev {opts}{device}" out = __salt__["cmd.run"](cmd, python_shell=False).splitlines() return dump(device, args) def wipe(device): """ Remove the filesystem information CLI Example: .. code-block:: bash salt '*' disk.wipe /dev/sda1 """ cmd = f"wipefs -a {device}" try: out = __salt__["cmd.run_all"](cmd, python_shell=False) except subprocess.CalledProcessError as err: return False if out["retcode"] == 0: return True else: log.error("Error wiping device %s: %s", device, out["stderr"]) return False def dump(device, args=None): """ Return all contents of dumpe2fs for a specified device device The device path to dump. args A list of attributes to return. Returns all by default. CLI Example: .. code-block:: bash salt '*' disk.dump /dev/sda1 """ cmd = ( "blockdev --getro --getsz --getss --getpbsz --getiomin --getioopt --getalignoff" " --getmaxsect --getsize --getsize64 --getra --getfra {}".format(device) ) ret = {} opts = [c[2:] for c in cmd.split() if c.startswith("--")] out = __salt__["cmd.run_all"](cmd, python_shell=False) if out["retcode"] == 0: lines = [line for line in out["stdout"].splitlines() if line] count = 0 for line in lines: ret[opts[count]] = line count = count + 1 if args: temp_ret = {} for arg in args: temp_ret[arg] = ret[arg] return temp_ret else: return ret else: return False def resize2fs(device): """ Resizes the filesystem. CLI Example: .. code-block:: bash salt '*' disk.resize2fs /dev/sda1 """ cmd = f"resize2fs {device}" try: out = __salt__["cmd.run_all"](cmd, python_shell=False) except subprocess.CalledProcessError as err: return False if out["retcode"] == 0: return True @salt.utils.decorators.path.which("sync") @salt.utils.decorators.path.which("mkfs") def format_( device, fs_type="ext4", inode_size=None, lazy_itable_init=None, fat=None, force=False, ): """ Format a filesystem onto a device .. versionadded:: 2016.11.0 device The device in which to create the new filesystem fs_type The type of filesystem to create inode_size Size of the inodes This option is only enabled for ext and xfs filesystems lazy_itable_init If enabled and the uninit_bg feature is enabled, the inode table will not be fully initialized by mke2fs. This speeds up filesystem initialization noticeably, but it requires the kernel to finish initializing the filesystem in the background when the filesystem is first mounted. If the option value is omitted, it defaults to 1 to enable lazy inode table zeroing. This option is only enabled for ext filesystems fat FAT size option. Can be 12, 16 or 32, and can only be used on fat or vfat filesystems. force Force mke2fs to create a filesystem, even if the specified device is not a partition on a block special device. This option is only enabled for ext and xfs filesystems This option is dangerous, use it with caution. CLI Example: .. code-block:: bash salt '*' disk.format /dev/sdX1 """ cmd = ["mkfs", "-t", str(fs_type)] if inode_size is not None: if fs_type[:3] == "ext": cmd.extend(["-i", str(inode_size)]) elif fs_type == "xfs": cmd.extend(["-i", f"size={inode_size}"]) if lazy_itable_init is not None: if fs_type[:3] == "ext": cmd.extend(["-E", f"lazy_itable_init={lazy_itable_init}"]) if fat is not None and fat in (12, 16, 32): if fs_type[-3:] == "fat": cmd.extend(["-F", fat]) if force: if fs_type[:3] == "ext": cmd.append("-F") elif fs_type == "xfs": cmd.append("-f") cmd.append(str(device)) mkfs_success = __salt__["cmd.retcode"](cmd, ignore_retcode=True) == 0 sync_success = __salt__["cmd.retcode"]("sync", ignore_retcode=True) == 0 return all([mkfs_success, sync_success]) @salt.utils.decorators.path.which_bin(["lsblk", "df"]) def fstype(device): """ Return the filesystem name of the specified device .. versionadded:: 2016.11.0 device The name of the device CLI Example: .. code-block:: bash salt '*' disk.fstype /dev/sdX1 """ if salt.utils.path.which("lsblk"): lsblk_out = __salt__["cmd.run"](f"lsblk -o fstype {device}").splitlines() if len(lsblk_out) > 1: fs_type = lsblk_out[1].strip() if fs_type: return fs_type if salt.utils.path.which("df"): # the fstype was not set on the block device, so inspect the filesystem # itself for its type if __grains__["kernel"] == "AIX" and os.path.isfile("/usr/sysv/bin/df"): df_out = __salt__["cmd.run"](f"/usr/sysv/bin/df -n {device}").split() if len(df_out) > 2: fs_type = df_out[2] if fs_type: return fs_type else: df_out = __salt__["cmd.run"](f"df -T {device}").splitlines() if len(df_out) > 1: fs_type = df_out[1] if fs_type: return fs_type return "" @salt.utils.decorators.path.which("hdparm") def _hdparm(args, failhard=True): """ Execute hdparm Fail hard when required return output when possible """ cmd = f"hdparm {args}" result = __salt__["cmd.run_all"](cmd) if result["retcode"] != 0: msg = "{}: {}".format(cmd, result["stderr"]) if failhard: raise CommandExecutionError(msg) else: log.warning(msg) return result["stdout"] @salt.utils.decorators.path.which("hdparm") def hdparms(disks, args="aAbBcCdgHiJkMmNnQrRuW"): """ Retrieve disk parameters. .. versionadded:: 2016.3.0 disks Single disk or list of disks to query. args Sequence of ``hdparm`` flags to fetch. CLI Example: .. code-block:: bash salt '*' disk.hdparms /dev/sda """ if isinstance(args, (list, tuple)): args = "".join(args) if not isinstance(disks, (list, tuple)): disks = [disks] out = {} for disk in disks: if not disk.startswith("/dev"): disk = f"/dev/{disk}" disk_data = {} for line in _hdparm(f"-{args} {disk}", False).splitlines(): line = line.strip() if not line or line == disk + ":": continue if ":" in line: key, vals = line.split(":", 1) key = re.sub(r" is$", "", key) elif "=" in line: key, vals = line.split("=", 1) else: continue key = key.strip().lower().replace(" ", "_") vals = vals.strip() rvals = [] if re.match(r"[0-9]+ \(.*\)", vals): vals = vals.split(" ") rvals.append(int(vals[0])) rvals.append(vals[1].strip("()")) else: valdict = {} for val in re.split(r"[/,]", vals.strip()): val = val.strip() try: val = int(val) rvals.append(val) except Exception: # pylint: disable=broad-except if "=" in val: deep_key, val = val.split("=", 1) deep_key = deep_key.strip() val = val.strip() if val: valdict[deep_key] = val elif val: rvals.append(val) if valdict: rvals.append(valdict) if not rvals: continue elif len(rvals) == 1: rvals = rvals[0] disk_data[key] = rvals out[disk] = disk_data return out @salt.utils.decorators.path.which("hdparm") def hpa(disks, size=None): """ Get/set Host Protected Area settings T13 INCITS 346-2001 (1367D) defines the BEER (Boot Engineering Extension Record) and PARTIES (Protected Area Run Time Interface Extension Services), allowing for a Host Protected Area on a disk. It's often used by OEMS to hide parts of a disk, and for overprovisioning SSD's .. warning:: Setting the HPA might clobber your data, be very careful with this on active disks! .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' disk.hpa /dev/sda salt '*' disk.hpa /dev/sda 5% salt '*' disk.hpa /dev/sda 10543256 """ hpa_data = {} for disk, data in hdparms(disks, "N").items(): visible, total, status = next(iter(data.values())) if visible == total or "disabled" in status: hpa_data[disk] = {"total": total} else: hpa_data[disk] = { "total": total, "visible": visible, "hidden": total - visible, } if size is None: return hpa_data for disk, data in hpa_data.items(): try: size = data["total"] - int(size) except Exception: # pylint: disable=broad-except if "%" in size: size = int(size.strip("%")) size = (100 - size) * data["total"] size /= 100 if size <= 0: size = data["total"] _hdparm(f"--yes-i-know-what-i-am-doing -Np{size} {disk}") def smart_attributes(dev, attributes=None, values=None): """ Fetch SMART attributes Providing attributes will deliver only requested attributes Providing values will deliver only requested values for attributes Default is the Backblaze recommended set (https://www.backblaze.com/blog/hard-drive-smart-stats/): (5,187,188,197,198) .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' disk.smart_attributes /dev/sda salt '*' disk.smart_attributes /dev/sda attributes=(5,187,188,197,198) """ if not dev.startswith("/dev/"): dev = "/dev/" + dev cmd = f"smartctl --attributes {dev}" smart_result = __salt__["cmd.run_all"](cmd, output_loglevel="quiet") if smart_result["retcode"] != 0: raise CommandExecutionError(smart_result["stderr"]) smart_result = iter(smart_result["stdout"].splitlines()) fields = [] for line in smart_result: if line.startswith("ID#"): fields = re.split(r"\s+", line.strip()) fields = [key.lower() for key in fields[1:]] break if values is not None: fields = [field if field in values else "_" for field in fields] smart_attr = {} for line in smart_result: if not re.match(r"[\s]*\d", line): break line = re.split(r"\s+", line.strip(), maxsplit=len(fields)) attr = int(line[0]) if attributes is not None and attr not in attributes: continue data = dict(zip(fields, line[1:])) try: del data["_"] except Exception: # pylint: disable=broad-except pass for field in data: val = data[field] try: val = int(val) except Exception: # pylint: disable=broad-except try: val = [int(value) for value in val.split(" ")] except Exception: # pylint: disable=broad-except pass data[field] = val smart_attr[attr] = data return smart_attr @salt.utils.decorators.path.which("iostat") def iostat(interval=1, count=5, disks=None): """ Gather and return (averaged) IO stats. .. versionadded:: 2016.3.0 .. versionchanged:: 2016.11.4 Added support for AIX CLI Example: .. code-block:: bash salt '*' disk.iostat 1 5 disks=sda """ if salt.utils.platform.is_linux(): return _iostat_linux(interval, count, disks) elif salt.utils.platform.is_freebsd(): return _iostat_fbsd(interval, count, disks) elif salt.utils.platform.is_aix(): return _iostat_aix(interval, count, disks) def _iostats_dict(header, stats): """ Transpose collected data, average it, stomp it in dict using header Use Decimals so we can properly calc & round, convert to float 'caus' we can't transmit Decimals over 0mq """ stats = [ float((sum(stat) / len(stat)).quantize(decimal.Decimal(".01"))) for stat in zip(*stats) ] stats = dict(zip(header, stats)) return stats def _iostat_fbsd(interval, count, disks): """ Tested on FreeBSD, quite likely other BSD's only need small changes in cmd syntax """ if disks is None: iostat_cmd = f"iostat -xC -w {interval} -c {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -x -w {interval} -c {count} {disks}" else: iostat_cmd = "iostat -x -w {} -c {} {}".format(interval, count, " ".join(disks)) sys_stats = [] dev_stats = collections.defaultdict(list) sys_header = [] dev_header = [] h_len = 1000 # randomly absurdly high ret = iter( __salt__["cmd.run_stdout"](iostat_cmd, output_loglevel="quiet").splitlines() ) for line in ret: if not line.startswith("device"): continue elif not dev_header: dev_header = line.split()[1:] while line is not False: line = next(ret, False) if not line or not line[0].isalnum(): break line = line.split() disk = line[0] stats = [decimal.Decimal(x) for x in line[1:]] # h_len will become smallest number of fields in stat lines if len(stats) < h_len: h_len = len(stats) dev_stats[disk].append(stats) iostats = {} # The header was longer than the smallest number of fields # Therefore the sys stats are hidden in there if h_len < len(dev_header): sys_header = dev_header[h_len:] dev_header = dev_header[0:h_len] for disk, stats in dev_stats.items(): if len(stats[0]) > h_len: sys_stats = [stat[h_len:] for stat in stats] dev_stats[disk] = [stat[0:h_len] for stat in stats] iostats["sys"] = _iostats_dict(sys_header, sys_stats) for disk, stats in dev_stats.items(): iostats[disk] = _iostats_dict(dev_header, stats) return iostats def _iostat_linux(interval, count, disks): if disks is None: iostat_cmd = f"iostat -x {interval} {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -xd {interval} {count} {disks}" else: iostat_cmd = "iostat -xd {} {} {}".format(interval, count, " ".join(disks)) sys_stats = [] dev_stats = collections.defaultdict(list) sys_header = [] dev_header = [] ret = iter( __salt__["cmd.run_stdout"](iostat_cmd, output_loglevel="quiet").splitlines() ) for line in ret: if line.startswith("avg-cpu:"): if not sys_header: sys_header = tuple(line.split()[1:]) line = [decimal.Decimal(x) for x in next(ret).split()] sys_stats.append(line) elif line.startswith("Device:"): if not dev_header: dev_header = tuple(line.split()[1:]) while line is not False: line = next(ret, False) if not line or not line[0].isalnum(): break line = line.split() disk = line[0] stats = [decimal.Decimal(x) for x in line[1:]] dev_stats[disk].append(stats) iostats = {} if sys_header: iostats["sys"] = _iostats_dict(sys_header, sys_stats) for disk, stats in dev_stats.items(): iostats[disk] = _iostats_dict(dev_header, stats) return iostats def _iostat_aix(interval, count, disks): """ AIX support to gather and return (averaged) IO stats. """ log.debug("AIX disk iostat entry") if disks is None: iostat_cmd = f"iostat -dD {interval} {count} " elif isinstance(disks, str): iostat_cmd = f"iostat -dD {disks} {interval} {count}" else: iostat_cmd = "iostat -dD {} {} {}".format(" ".join(disks), interval, count) ret = {} procn = None fields = [] disk_name = "" disk_mode = "" dev_stats = collections.defaultdict(list) for line in __salt__["cmd.run"](iostat_cmd).splitlines(): # Note: iostat -dD is per-system # # root@l490vp031_pub:~/devtest# iostat -dD hdisk6 1 3 # # System configuration: lcpu=8 drives=1 paths=2 vdisks=2 # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 0.0 0.0 0.0 0.0 0.0 # read: rps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 9.6 16.4K 4.0 16.4K 0.0 # read: rps avgserv minserv maxserv timeouts fails # 4.0 4.9 0.3 9.9 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- # # hdisk6 xfer: %tm_act bps tps bread bwrtn # 0.0 0.0 0.0 0.0 0.0 # read: rps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.3 9.9 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.0 0.0 0.0 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.0 0.0 0.0 0.0 0.0 0.0 # -------------------------------------------------------------------------------- if not line or line.startswith("System") or line.startswith("-----------"): continue if not re.match(r"\s", line): # seen disk name dsk_comps = line.split(":") dsk_firsts = dsk_comps[0].split() disk_name = dsk_firsts[0] disk_mode = dsk_firsts[1] fields = dsk_comps[1].split() if disk_name not in dev_stats.keys(): dev_stats[disk_name] = [] procn = len(dev_stats[disk_name]) dev_stats[disk_name].append({}) dev_stats[disk_name][procn][disk_mode] = {} dev_stats[disk_name][procn][disk_mode]["fields"] = fields dev_stats[disk_name][procn][disk_mode]["stats"] = [] continue if ":" in line: comps = line.split(":") fields = comps[1].split() disk_mode = comps[0].lstrip() if disk_mode not in dev_stats[disk_name][0].keys(): dev_stats[disk_name][0][disk_mode] = {} dev_stats[disk_name][0][disk_mode]["fields"] = fields dev_stats[disk_name][0][disk_mode]["stats"] = [] else: line = line.split() stats = [_parse_numbers(x) for x in line[:]] dev_stats[disk_name][0][disk_mode]["stats"].append(stats) iostats = {} for disk, list_modes in dev_stats.items(): iostats[disk] = {} for modes in list_modes: for disk_mode in modes.keys(): fields = modes[disk_mode]["fields"] stats = modes[disk_mode]["stats"] iostats[disk][disk_mode] = _iostats_dict(fields, stats) return iostats def get_fstype_from_path(path): """ Return the filesystem type of the underlying device for a specified path. .. versionadded:: 3006.0 path The path for the function to evaluate. CLI Example: .. code-block:: bash salt '*' disk.get_fstype_from_path /root """ dev = __salt__["mount.get_device_from_path"](path) return fstype(dev)