#!/usr/bin/env python3 # pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=missing-function-docstring import json import subprocess import pytest from selfprivacy_api.utils.block_devices import ( BlockDevice, BlockDevices, get_block_device, resize_block_device, ) from tests.common import read_json SINGLE_LSBLK_OUTPUT = b""" { "blockdevices": [ { "name": "sda1", "path": "/dev/sda1", "fsavail": "4614107136", "fssize": "19814920192", "fstype": "ext4", "fsused": "14345314304", "mountpoints": [ "/nix/store", "/" ], "label": null, "uuid": "ec80c004-baec-4a2c-851d-0e1807135511", "size": 20210236928, "model": null, "serial": null, "type": "part" } ] } """ @pytest.fixture def lsblk_singular_mock(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, return_value=SINGLE_LSBLK_OUTPUT ) return mock @pytest.fixture def failed_check_output_mock(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, side_effect=subprocess.CalledProcessError( returncode=1, cmd=["some", "command"] ), ) return mock @pytest.fixture def only_root_in_userdata(mocker, datadir): mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "only_root.json") assert read_json(datadir / "only_root.json")["volumes"][0]["device"] == "/dev/sda1" assert ( read_json(datadir / "only_root.json")["volumes"][0]["mountPoint"] == "/volumes/sda1" ) assert read_json(datadir / "only_root.json")["volumes"][0]["fsType"] == "ext4" return datadir @pytest.fixture def no_devices_in_userdata(mocker, datadir): mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "no_devices.json") assert read_json(datadir / "no_devices.json")["volumes"] == [] return datadir @pytest.fixture def undefined_devices_in_userdata(mocker, datadir): mocker.patch("selfprivacy_api.utils.USERDATA_FILE", new=datadir / "undefined.json") assert "volumes" not in read_json(datadir / "undefined.json") return datadir def test_create_block_device_object(lsblk_singular_mock, authorized_client): output = get_block_device("sda1") assert lsblk_singular_mock.call_count == 1 assert lsblk_singular_mock.call_args[0][0] == [ "lsblk", "-J", "-b", "-o", "NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE", "/dev/sda1", ] assert output == json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0] def test_resize_block_device(lsblk_singular_mock, authorized_client): result = resize_block_device("sdb") assert result is True assert lsblk_singular_mock.call_count == 1 assert lsblk_singular_mock.call_args[0][0] == [ "resize2fs", "sdb", ] def test_resize_block_device_failed(failed_check_output_mock, authorized_client): result = resize_block_device("sdb") assert result is False assert failed_check_output_mock.call_count == 1 assert failed_check_output_mock.call_args[0][0] == [ "resize2fs", "sdb", ] VOLUME_LSBLK_OUTPUT = b""" { "blockdevices": [ { "name": "sdb", "path": "/dev/sdb", "fsavail": "11888545792", "fssize": "12573614080", "fstype": "ext4", "fsused": "24047616", "mountpoints": [ "/volumes/sdb" ], "label": null, "uuid": "fa9d0026-ee23-4047-b8b1-297ae16fa751", "size": 12884901888, "model": "Volume", "serial": "21378102", "type": "disk" } ] } """ def test_create_block_device(lsblk_singular_mock, authorized_client): block_device = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) assert block_device.name == "sdb" assert block_device.path == "/dev/sdb" assert block_device.fsavail == "11888545792" assert block_device.fssize == "12573614080" assert block_device.fstype == "ext4" assert block_device.fsused == "24047616" assert block_device.mountpoints == ["/volumes/sdb"] assert block_device.label is None assert block_device.uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751" assert block_device.size == "12884901888" assert block_device.model == "Volume" assert block_device.serial == "21378102" assert block_device.type == "disk" assert block_device.locked is False assert str(block_device) == "sdb" assert ( repr(block_device) == "" ) assert hash(block_device) == hash("sdb") def test_block_devices_equal(lsblk_singular_mock, authorized_client): block_device = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) block_device2 = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) assert block_device == block_device2 @pytest.fixture def resize_block_mock(mocker): mock = mocker.patch( "selfprivacy_api.utils.block_devices.resize_block_device", autospec=True, return_value=True, ) return mock def test_call_resize_from_block_device( lsblk_singular_mock, resize_block_mock, authorized_client ): block_device = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) block_device.resize() assert resize_block_mock.call_count == 1 assert resize_block_mock.call_args[0][0] == "/dev/sdb" assert lsblk_singular_mock.call_count == 0 def test_get_stats_from_block_device(lsblk_singular_mock, authorized_client): block_device = BlockDevice(json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0]) stats = block_device.stats() assert stats == { "name": "sda1", "path": "/dev/sda1", "fsavail": "4614107136", "fssize": "19814920192", "fstype": "ext4", "fsused": "14345314304", "mountpoints": ["/nix/store", "/"], "label": None, "uuid": "ec80c004-baec-4a2c-851d-0e1807135511", "size": "20210236928", "model": None, "serial": None, "type": "part", } assert lsblk_singular_mock.call_count == 1 assert lsblk_singular_mock.call_args[0][0] == [ "lsblk", "-J", "-b", "-o", "NAME,PATH,FSAVAIL,FSSIZE,FSTYPE,FSUSED,MOUNTPOINTS,LABEL,UUID,SIZE,MODEL,SERIAL,TYPE", "/dev/sda1", ] def test_mount_block_device( lsblk_singular_mock, only_root_in_userdata, authorized_client ): block_device = BlockDevice(json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0]) result = block_device.mount() assert result is False volume = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) result = volume.mount() assert result is True assert ( read_json(only_root_in_userdata / "only_root.json")["volumes"][1]["device"] == "/dev/sdb" ) assert ( read_json(only_root_in_userdata / "only_root.json")["volumes"][1]["mountPoint"] == "/volumes/sdb" ) assert ( read_json(only_root_in_userdata / "only_root.json")["volumes"][1]["fsType"] == "ext4" ) def test_mount_block_device_when_undefined( lsblk_singular_mock, undefined_devices_in_userdata, authorized_client ): block_device = BlockDevice(json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0]) result = block_device.mount() assert result is True assert ( read_json(undefined_devices_in_userdata / "undefined.json")["volumes"][0][ "device" ] == "/dev/sda1" ) assert ( read_json(undefined_devices_in_userdata / "undefined.json")["volumes"][0][ "mountPoint" ] == "/volumes/sda1" ) assert ( read_json(undefined_devices_in_userdata / "undefined.json")["volumes"][0][ "fsType" ] == "ext4" ) def test_unmount_block_device( lsblk_singular_mock, only_root_in_userdata, authorized_client ): block_device = BlockDevice(json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0]) result = block_device.unmount() assert result is True volume = BlockDevice(json.loads(VOLUME_LSBLK_OUTPUT)["blockdevices"][0]) result = volume.unmount() assert result is False assert len(read_json(only_root_in_userdata / "only_root.json")["volumes"]) == 0 def test_unmount_block_device_when_undefined( lsblk_singular_mock, undefined_devices_in_userdata, authorized_client ): block_device = BlockDevice(json.loads(SINGLE_LSBLK_OUTPUT)["blockdevices"][0]) result = block_device.unmount() assert result is False assert ( len(read_json(undefined_devices_in_userdata / "undefined.json")["volumes"]) == 0 ) FULL_LSBLK_OUTPUT = b""" { "blockdevices": [ { "name": "sda", "path": "/dev/sda", "fsavail": null, "fssize": null, "fstype": null, "fsused": null, "mountpoints": [ null ], "label": null, "uuid": null, "size": 20480786432, "model": "QEMU HARDDISK", "serial": "drive-scsi0-0-0-0", "type": "disk", "children": [ { "name": "sda1", "path": "/dev/sda1", "fsavail": "4605702144", "fssize": "19814920192", "fstype": "ext4", "fsused": "14353719296", "mountpoints": [ "/nix/store", "/" ], "label": null, "uuid": "ec80c004-baec-4a2c-851d-0e1807135511", "size": 20210236928, "model": null, "serial": null, "type": "part" },{ "name": "sda14", "path": "/dev/sda14", "fsavail": null, "fssize": null, "fstype": null, "fsused": null, "mountpoints": [ null ], "label": null, "uuid": null, "size": 1048576, "model": null, "serial": null, "type": "part" },{ "name": "sda15", "path": "/dev/sda15", "fsavail": null, "fssize": null, "fstype": "vfat", "fsused": null, "mountpoints": [ null ], "label": null, "uuid": "6B29-5BA7", "size": 268435456, "model": null, "serial": null, "type": "part" } ] },{ "name": "sdb", "path": "/dev/sdb", "fsavail": "11888545792", "fssize": "12573614080", "fstype": "ext4", "fsused": "24047616", "mountpoints": [ "/volumes/sdb" ], "label": null, "uuid": "fa9d0026-ee23-4047-b8b1-297ae16fa751", "size": 12884901888, "model": "Volume", "serial": "21378102", "type": "disk" },{ "name": "sr0", "path": "/dev/sr0", "fsavail": null, "fssize": null, "fstype": null, "fsused": null, "mountpoints": [ null ], "label": null, "uuid": null, "size": 1073741312, "model": "QEMU DVD-ROM", "serial": "QM00003", "type": "rom" } ] } """ @pytest.fixture def lsblk_full_mock(mocker): mock = mocker.patch( "subprocess.check_output", autospec=True, return_value=FULL_LSBLK_OUTPUT ) BlockDevices().update() return mock def test_get_block_devices(lsblk_full_mock, authorized_client): block_devices = BlockDevices().get_block_devices() assert len(block_devices) == 2 devices_by_name = {device.name: device for device in block_devices} sda1 = devices_by_name["sda1"] sdb = devices_by_name["sdb"] assert sda1.name == "sda1" assert sda1.path == "/dev/sda1" assert sda1.fsavail == "4605702144" assert sda1.fssize == "19814920192" assert sda1.fstype == "ext4" assert sda1.fsused == "14353719296" assert sda1.mountpoints == ["/nix/store", "/"] assert sda1.label is None assert sda1.uuid == "ec80c004-baec-4a2c-851d-0e1807135511" assert sda1.size == "20210236928" assert sda1.model is None assert sda1.serial is None assert sda1.type == "part" assert sdb.name == "sdb" assert sdb.path == "/dev/sdb" assert sdb.fsavail == "11888545792" assert sdb.fssize == "12573614080" assert sdb.fstype == "ext4" assert sdb.fsused == "24047616" assert sdb.mountpoints == ["/volumes/sdb"] assert sdb.label is None assert sdb.uuid == "fa9d0026-ee23-4047-b8b1-297ae16fa751" assert sdb.size == "12884901888" assert sdb.model == "Volume" assert sdb.serial == "21378102" assert sdb.type == "disk" def test_get_block_device(lsblk_full_mock, authorized_client): block_device = BlockDevices().get_block_device("sda1") assert block_device is not None assert block_device.name == "sda1" assert block_device.path == "/dev/sda1" assert block_device.fsavail == "4605702144" assert block_device.fssize == "19814920192" assert block_device.fstype == "ext4" assert block_device.fsused == "14353719296" assert block_device.mountpoints == ["/nix/store", "/"] assert block_device.label is None assert block_device.uuid == "ec80c004-baec-4a2c-851d-0e1807135511" assert block_device.size == "20210236928" assert block_device.model is None assert block_device.serial is None assert block_device.type == "part" def test_get_nonexistent_block_device(lsblk_full_mock, authorized_client): block_device = BlockDevices().get_block_device("sda2") assert block_device is None def test_get_block_devices_by_mountpoint(lsblk_full_mock, authorized_client): block_devices = BlockDevices().get_block_devices_by_mountpoint("/nix/store") assert len(block_devices) == 1 assert block_devices[0].name == "sda1" assert block_devices[0].path == "/dev/sda1" assert block_devices[0].fsavail == "4605702144" assert block_devices[0].fssize == "19814920192" assert block_devices[0].fstype == "ext4" assert block_devices[0].fsused == "14353719296" assert block_devices[0].mountpoints == ["/nix/store", "/"] assert block_devices[0].label is None assert block_devices[0].uuid == "ec80c004-baec-4a2c-851d-0e1807135511" assert block_devices[0].size == "20210236928" assert block_devices[0].model is None assert block_devices[0].serial is None assert block_devices[0].type == "part" def test_get_block_devices_by_mountpoint_no_match(lsblk_full_mock, authorized_client): block_devices = BlockDevices().get_block_devices_by_mountpoint("/foo") assert len(block_devices) == 0 def test_get_root_block_device(lsblk_full_mock, authorized_client): block_device = BlockDevices().get_root_block_device() assert block_device is not None assert block_device.name == "sda1" assert block_device.path == "/dev/sda1" assert block_device.fsavail == "4605702144" assert block_device.fssize == "19814920192" assert block_device.fstype == "ext4" assert block_device.fsused == "14353719296" assert block_device.mountpoints == ["/nix/store", "/"] assert block_device.label is None assert block_device.uuid == "ec80c004-baec-4a2c-851d-0e1807135511" assert block_device.size == "20210236928" assert block_device.model is None assert block_device.serial is None assert block_device.type == "part" # Unassuming sanity check, yes this did fail def test_get_real_devices(): block_devices = BlockDevices().get_block_devices() assert block_devices is not None assert len(block_devices) > 0 # Unassuming sanity check def test_get_real_root_device(): devices = BlockDevices().get_block_devices() try: block_device = BlockDevices().get_root_block_device() except Exception as e: raise Exception("cannot get root device:", e, "devices found:", devices) assert block_device is not None assert block_device.name is not None assert block_device.name != "" def test_get_real_root_device_raw(authorized_client): block_device = BlockDevices().get_root_block_device() assert block_device is not None assert block_device.name is not None assert block_device.name != ""