From 359bca71effb7f73deb36a442cb879a61a315d32 Mon Sep 17 00:00:00 2001 From: mulhern Date: Wed, 14 Feb 2024 13:11:04 -0500 Subject: [PATCH] Add script to get Stratis info from devicemapper path Signed-off-by: mulhern --- .githooks/pre-commit | 1 + .github/workflows/main.yml | 17 +++ .isort.cfg | 14 +++ Makefile | 17 ++- src/bin/utils/stratis-decode-dm | 203 ++++++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 .isort.cfg create mode 100755 src/bin/utils/stratis-decode-dm diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 910791057c..4d4cda004f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -11,6 +11,7 @@ make fmt-ci && make clippy && make yamllint && make tmtlint && + make pylint && make check-typos || exit 1 export PYTHONPATH=$PWD/tests/client-dbus/src diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 332cb4de29..643efcdf2b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,6 +54,7 @@ jobs: - name: Install dependencies for Fedora run: > dnf install -y + black clang curl cryptsetup-devel @@ -62,6 +63,7 @@ jobs: libblkid-devel make ncurses + python3-isort systemd-devel - uses: dtolnay/rust-toolchain@master with: @@ -235,6 +237,21 @@ jobs: - name: Run shell check run: make -f Makefile fmt-shell-ci + pylint: + runs-on: ubuntu-22.04 + container: + image: fedora:39 # CURRENT DEVELOPMENT ENVIRONMENT + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: > + dnf install -y + make + pylint + python3-dbus + - name: Run pylint + run: make -f Makefile pylint + python-based-tests: runs-on: ubuntu-22.04 container: diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000000..29b9c7439f --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,14 @@ +[settings] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 + +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCAL,LOCALFOLDER + +import_heading_future=isort: FUTURE +import_heading_stdlib=isort: STDLIB +import_heading_thirdparty=isort: THIRDPARTY +import_heading_firstparty=isort: FIRSTPARTY +import_heading_local=isort: LOCAL diff --git a/Makefile b/Makefile index 438378fe7c..7b47bf9d70 100644 --- a/Makefile +++ b/Makefile @@ -175,10 +175,14 @@ check-typos: ## Run cargo fmt fmt: fmt-macros cargo fmt + isort ./src/bin/utils/stratis-decode-dm + black ./src/bin/utils/stratis-decode-dm ## Run cargo fmt for CI jobs fmt-ci: fmt-macros-ci cargo fmt -- --check + isort --diff --check-only ./src/bin/utils/stratis-decode-dm + black ./src/bin/utils/stratis-decode-dm --check ## Run cargo fmt for stratisd_proc_macros fmt-macros: @@ -333,6 +337,11 @@ install-systemd-cfg: sed 's|@LIBEXECDIR@|$(LIBEXECDIR)|' systemd/stratisd-min-postinitrd.service.in > $(DESTDIR)$(UNITDIR)/stratisd-min-postinitrd.service sed 's|@UNITEXECDIR@|$(UNITEXECDIR)|' systemd/stratis-fstab-setup@.service.in > $(DESTDIR)$(UNITDIR)/stratis-fstab-setup@.service +## Install scripts +install-scripts: + mkdir -p $(DESTDIR)$(BINDIR) + $(INSTALL) -Dpm0755 -t $(DESTDIR)$(BINDIR) src/bin/utils/stratis-decode-dm + ## Install binaries install-binaries: mkdir -p $(DESTDIR)$(BINDIR) @@ -365,7 +374,7 @@ install-daemons: $(INSTALL) -Dpm0755 -t $(DESTDIR)$(LIBEXECDIR) target/$(PROFILEDIR)/stratisd-min ## Install all stratisd files -install: install-udev-cfg install-man-cfg install-dbus-cfg install-dracut-cfg install-systemd-cfg install-binaries install-udev-binaries install-fstab-script install-daemons +install: install-udev-cfg install-man-cfg install-dbus-cfg install-dracut-cfg install-systemd-cfg install-scripts install-binaries install-udev-binaries install-fstab-script install-daemons ## Build all Rust artifacts build-all-rust: build build-min build-utils build-udev-utils stratisd-tools @@ -504,6 +513,10 @@ clippy-no-ipc: clippy: clippy-macros clippy-min clippy-udev-utils clippy-no-ipc clippy-utils RUSTFLAGS="${DENY}" cargo clippy ${CLIPPY_OPTS} -- ${CLIPPY_DENY} ${CLIPPY_PEDANTIC} ${CLIPPY_PEDANTIC_USELESS} +## Lint Python parts of the source code +pylint: + pylint --disable=invalid-name ./src/bin/utils/stratis-decode-dm + .PHONY: audit audit-all-rust @@ -541,10 +554,12 @@ clippy: clippy-macros clippy-min clippy-udev-utils clippy-no-ipc clippy-utils install-dracut-cfg install-fstab-script install-man-cfg + install-scripts install-systemd-cfg install-udev-binaries install-udev-cfg license + pylint test test-valgrind test-loop diff --git a/src/bin/utils/stratis-decode-dm b/src/bin/utils/stratis-decode-dm new file mode 100755 index 0000000000..86e9269ec0 --- /dev/null +++ b/src/bin/utils/stratis-decode-dm @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Script to map from devicemapper name to Stratis values +""" + +# isort: STDLIB +import argparse +import pathlib +from enum import Enum +from uuid import UUID + +# isort: THIRDPARTY +import dbus + +_REVISION_NUMBER = 7 +_REVISION = f"r{_REVISION_NUMBER}" +_BUS_NAME = "org.storage.stratis3" +_TOP_OBJECT = "/org/storage/stratis3" +_OBJECT_MANAGER = "org.freedesktop.DBus.ObjectManager" +_TIMEOUT = 120000 +_POOL_IFACE = f"{_BUS_NAME}.pool.{_REVISION}" +_FS_IFACE = f"{_BUS_NAME}.filesystem.{_REVISION}" +_DEV_PATH = "/dev/stratis" + + +class OutputMode(Enum): + """ + Output mode choices. + """ + + FILESYSTEM_NAME = "filesystem-name" + POOL_NAME = "pool-name" + SYMLINK = "symlink" + + def __str__(self): + return self.value + + +def _parse_dm_name(dmname): + """ + Parse a Stratis filesystem devicemapper name. + + :param str dmname: the devicemapper name of the filesystem device + :returns: the pool and filesystem UUID + :rtype: UUID * UUID + """ + try: + (stratis, format_version, pool_uuid, thin, fs, filesystem_uuid) = dmname.split( + "-" + ) + except ValueError as err: + raise RuntimeError( + f"error parsing Stratis filesystem devicemapper name {dmname}" + ) from err + + if stratis != "stratis" or format_version != "1" or thin != "thin" or fs != "fs": + raise RuntimeError( + f"error parsing Stratis filesystem devicemapper name {dmname}" + ) + + return (UUID(pool_uuid), UUID(filesystem_uuid)) + + +def _extract_dm_name(dmpath): + """ + Extract the devicemapper name from the devicemapper path. + + :param Path dmpath: The devicemapper path. + :returns: devicemapper name + :rtype: str + """ + + assert dmpath.is_absolute(), "parser ensures absolute path" + + try: + (_, dev, mapper, name) = dmpath.parts + except ValueError as err: + raise RuntimeError( + f"error decomposing Stratis filesystem devicemapper path: {dmpath}" + ) from err + + if dev != "dev" or mapper != "mapper": + raise RuntimeError( + f"error decomposing Stratis filesystem devicemapper path: {dmpath}" + ) + + return name + + +def _get_managed_objects(): + """ + Get managed objects for stratis + :return: A dict, Keys are object paths with dicts containing interface + names mapped to property dicts. + Property dicts map names to values. + """ + object_manager = dbus.Interface( + dbus.SystemBus().get_object(_BUS_NAME, _TOP_OBJECT), + _OBJECT_MANAGER, + ) + return object_manager.GetManagedObjects(timeout=_TIMEOUT) + + +def _get_parser(): + """ + Build a parser for this script. + """ + + def _abs_path(path): + parsed_path = pathlib.Path(path) + if not parsed_path.is_absolute(): + raise argparse.ArgumentTypeError( + f"{path} must be specified as an absolute path" + ) + + return parsed_path + + parser = argparse.ArgumentParser( + description="Utility that maps from Stratis filesystem devicemapper path to a Stratis value" + ) + parser.add_argument( + "path", + help="The absolute path of the devicemapper device ('/dev/mapper/')", + metavar="PATH", + type=_abs_path, + ) + parser.add_argument( + "--output", + choices=list(OutputMode), + help="Stratis value to print", + type=OutputMode, + required=True, + ) + return parser + + +def main(): + """ + The main method. + """ + parser = _get_parser() + namespace = parser.parse_args() + + dm_name = _extract_dm_name(namespace.path) + + (pool_uuid, filesystem_uuid) = _parse_dm_name(dm_name) + + managed_objects = _get_managed_objects() + + (pool_uuid_str, filesystem_uuid_str) = (pool_uuid.hex, filesystem_uuid.hex) + + pool_name = next( + ( + obj_data[_POOL_IFACE]["Name"] + for obj_data in managed_objects.values() + if _POOL_IFACE in obj_data + and obj_data[_POOL_IFACE]["Uuid"] == pool_uuid_str + ), + None, + ) + + filesystem_name = next( + ( + obj_data[_FS_IFACE]["Name"] + for obj_data in managed_objects.values() + if _FS_IFACE in obj_data + and obj_data[_FS_IFACE]["Uuid"] == filesystem_uuid_str + ), + None, + ) + + if namespace.output is OutputMode.SYMLINK: + if pool_name is None: + raise RuntimeError( + "Pool name could not be found; can not synthesize Stratis filesystem symlink" + ) + if filesystem_name is None: + raise RuntimeError( + "Filesystem name could not be found; can not synthesize Stratis filesystem symlink" + ) + print(pathlib.Path(_DEV_PATH, pool_name, filesystem_name)) + + elif namespace.output is OutputMode.FILESYSTEM_NAME: + if filesystem_name is None: + raise RuntimeError("Filesystem name could not be found") + print(filesystem_name) + + elif namespace.output is OutputMode.POOL_NAME: + if filesystem_name is None: + raise RuntimeError("Pool name could not be found") + print(pool_name) + + else: + assert False, "unreachable" + + +if __name__ == "__main__": + main()