From 7ba5700dc1c08735da0dd897bcf94ae30d294af1 Mon Sep 17 00:00:00 2001 From: Nir Soffer Date: May 14 2019 19:57:33 +0000 Subject: tests: Start tests for 4k storage We added API for using 4k sector size and various alignments, but there are no tests for the new APIs with disks using 4k sector size. Testing this is complicated since creating a block device and mounting file systems requires root, and we like to run the tests as a regular user. Add new tests for writing and reading lockspace and resource using 4k storage provided by the user. If a path is not specified the tests are skipped. To run the tests, you need to create a block device using 4k sector size and optionally create a file system and mount it. The process is explained in README.dev. Then pass the path of the block device or file on the new file system to the tests: USER_4K_PATH=/dev/loop2 tox -e py27 Adding and removing lockspace and acquiring resources is not tested yet with 4k storage. Signed-off-by: Nir Soffer --- diff --git a/README.dev b/README.dev index 3519993..bfb170c 100644 --- a/README.dev +++ b/README.dev @@ -24,3 +24,47 @@ To run only test from some modules: To run only tests matching the substring "foo": $ tox -- -k foo + + +Testing 4K support +================== + +This requires manual setup for creating a block device using 4k sector size, +optionally creating and mounting a file system, and passing the test path to +tests. The setup must be done as root, but the tests can run later as the +current user. + +Common setup +------------ + +1. Create a backing file: + + $ truncate -s 1G /var/tmp/backing + +2. Create a loop device with 4k sector size: + + $ sudo losetup -f /var/tmp/backing --show --sector-size=4096 + /dev/loop2 + +3. Change the device (or mountpoint) owner to current user + + $ sudo chown $USER:$USER /dev/loop2 + +Testing 4k block device +----------------------- + +Run the tests with USER_4K_PATH environment variable: + + $ USER_4K_PATH=/dev/loop2 tox -e py27 + +Testing 4k file system +---------------------- + +To test file system on top of 4k block device, create a file system on the +device, mount it, and create a file for testing lockspace or resources: + + $ sudo mkfs.xfs /dev/loop2 + $ sudo mount /dev/loop2 /tmp/sanlock-4k + $ sudo chown $USER:$USER /tmp/sanlock-4k + $ truncate -s 128m /tmp/sanlock-4k/disk + $ USER_4K_PATH=/tmp/sanlock-4k/disk tox -e py27 diff --git a/tests/conftest.py b/tests/conftest.py index 6b8b0e8..bb95d45 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,9 @@ Fixtures for sanlock testing. """ +import os +import stat + import pytest from . import util @@ -21,3 +24,36 @@ def sanlock_daemon(): # which takes about 3 seconds, slowing down the tests. p.kill() p.wait() + + +@pytest.fixture +def user_4k_path(): + """ + A path to block device or file on file system on top of 4k block device, + provided by the user. + + The user must create the block device or the file system before running the + tests, and specify the path to the file in the USER_4K_PATH environment + variable. + + If USER_4K_PATH was not specified, tests using this fixture will be skipped. + If USER_4K_PATH was specified but does not exist, or is not a file or block + device, RuntimeError is raised. + + Return path to the user specified file. + """ + path = os.environ.get("USER_4K_PATH") + if path is None: + pytest.skip("USER_4K_PATH pointing to a 4k block device or file was " + "not specified") + + if not os.path.exists(path): + raise RuntimeError("USER_4K_PATH {!r} does not exist".format(path)) + + mode = os.stat(path).st_mode + if not (stat.S_ISBLK(mode) or stat.S_ISREG(mode)): + raise RuntimeError( + "USER_4K_PATH {!r} is not a block device or regular file" + .format(path)) + + return path diff --git a/tests/python_test.py b/tests/python_test.py index 1a6bbbd..eb93762 100644 --- a/tests/python_test.py +++ b/tests/python_test.py @@ -25,6 +25,7 @@ MIN_RES_SIZE = 1024**2 ALIGNMENT_1M = 1024**2 SECTOR_SIZE_512 = 512 +SECTOR_SIZE_4K = 4096 @pytest.mark.parametrize("size,offset", [ @@ -66,6 +67,35 @@ def test_write_lockspace(tmpdir, sanlock_daemon, size, offset): util.check_guard(path, size) +@pytest.mark.parametrize("align", sanlock.ALIGN_SIZE) +def test_write_lockspace_4k(user_4k_path, sanlock_daemon, align): + + # Poison lockspace area, ensuring that previous tests will not break this + # test, and sanlock does not write beyond the lockspace area. + with io.open(user_4k_path, "rb+") as f: + f.write(align * b"x") + util.write_guard(user_4k_path, align) + + sanlock.write_lockspace( + "name", user_4k_path, iotimeout=1, align=align, sector=SECTOR_SIZE_4K) + + ls = sanlock.read_lockspace( + user_4k_path, align=align, sector=SECTOR_SIZE_4K) + + assert ls == {"iotimeout": 1, "lockspace": "name"} + + acquired = sanlock.inq_lockspace("name", 1, user_4k_path, wait=False) + assert acquired is False + + # Verify that lockspace was written. + with io.open(user_4k_path, "rb") as f: + magic, = struct.unpack("< I", f.read(4)) + assert magic == constants.DELTA_DISK_MAGIC + + # Check that sanlock did not write beyond the lockspace area. + util.check_guard(user_4k_path, align) + + @pytest.mark.parametrize("size,offset", [ # Smallest offset. (MIN_RES_SIZE, 0), @@ -113,6 +143,41 @@ def test_write_resource(tmpdir, sanlock_daemon, size, offset): util.check_guard(path, size) +@pytest.mark.parametrize("align", sanlock.ALIGN_SIZE) +def test_write_resource_4k(sanlock_daemon, user_4k_path, align): + disks = [(user_4k_path, 0)] + + # Poison resource area, ensuring that previous tests will not break this + # test, and sanlock does not write beyond the lockspace area. + with io.open(user_4k_path, "rb+") as f: + f.write(align * b"x") + util.write_guard(user_4k_path, align) + + sanlock.write_resource( + "ls_name", "res_name", disks, align=align, sector=SECTOR_SIZE_4K) + + res = sanlock.read_resource( + user_4k_path, align=align, sector=SECTOR_SIZE_4K) + + assert res == { + "lockspace": "ls_name", + "resource": "res_name", + "version": 0 + } + + owners = sanlock.read_resource_owners( + "ls_name", "res_name", disks, align=align, sector=SECTOR_SIZE_4K) + assert owners == [] + + # Verify that resource was written. + with io.open(user_4k_path, "rb") as f: + magic, = struct.unpack("< I", f.read(4)) + assert magic == constants.PAXOS_DISK_MAGIC + + # Check that sanlock did not write beyond the lockspace area. + util.check_guard(user_4k_path, align) + + @pytest.mark.parametrize("size,offset", [ # Smallest offset. (MIN_RES_SIZE, 0), diff --git a/tests/util.py b/tests/util.py index a6b53e3..0695d8c 100644 --- a/tests/util.py +++ b/tests/util.py @@ -13,6 +13,9 @@ import time TESTDIR = os.path.dirname(__file__) SANLOCK = os.path.join(TESTDIR, os.pardir, "src", "sanlock") +GUARD = b"X" +GUARD_SIZE = 4096 + class TimeoutExpired(Exception): """ Raised when timeout expired """ @@ -97,28 +100,37 @@ def wait_for_termination(p, timeout): time.sleep(0.05) -def create_file(path, size, guard=b"X", guard_size=4096): +def create_file(path, size, guard=True): """ Create sparse file of size bytes. - If guard is set, add a guard area after the end of the file and fill it - with guard bytes. This allows testing that the code under test do not write - anything after the end of the file. + If guard is True, add a guard area beyond the end of the file. """ with io.open(path, "wb") as f: f.truncate(size) - if guard: - f.seek(size) - f.write(guard * guard_size) + + if guard: + write_guard(path, size) + + +def write_guard(path, offset): + """ + Write guard areas at offset and fill with guard byte. + + Use check_guard() to verify that nothing was written to the guard area. + """ + with io.open(path, "rb+") as f: + f.seek(offset) + f.write(GUARD * GUARD_SIZE) -def check_guard(path, size, guard=b"X", guard_size=4096): +def check_guard(path, offset): """ - Assert that a file ends with a guard area filled with guard bytes. + Assert that guard area at offset was not modified. """ with io.open(path, "rb") as f: - f.seek(size) - assert f.read() == guard * guard_size + f.seek(offset) + assert f.read(GUARD_SIZE) == GUARD * GUARD_SIZE def check_rindex_entry(entry, name, offset=None, flags=None): diff --git a/tox.ini b/tox.ini index 1c2349e..d21f2f3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ skipsdist = True skip_missing_interpreters = True [testenv] -passenv = USER +passenv = * setenv = LD_LIBRARY_PATH={env:PWD}/wdmd:{env:PWD}/src SANLOCK_PRIVILEGED=0