forked from nirs/userstorage
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This version is based on vdsm tests/storage/userstorage.py, which is based on the older sanlock tests/storage.py tool. Improvements compared with vdsm version: - Create a package - Separate configuration from the tool, so the same tool can be used by other projects, or different configuration can be used in different environments. - Make it easier to run using python -m userstorage - Refine storage directory layout. - Remove oVirt CI policy from the tool; policy should be implemented in configuration files, not in the tool. - Add required option, for marking some configuration as optional - Improve error handling so setup code can handled both unsupported configuration and failed setup in a generic way.
- Loading branch information
Showing
13 changed files
with
807 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,125 @@ | ||
# userstorage | ||
Helper for setting up storage for tests | ||
|
||
Helper for setting up storage for tests. | ||
|
||
|
||
## Overview | ||
|
||
Some tests need more than a temporary directory on the local file | ||
system. One example is testing block device with 4k sector size, or | ||
testing a filesystem on top of such a block device. | ||
|
||
You can create storage using loop devices and mounts in test fixtures, | ||
but creating devices and mounts requires root. Do you really want to run | ||
all your tests as root, when the code under test should not run as root? | ||
|
||
The userstorage tool solves this problem by creating storage for tests | ||
before running the tests, and making the storage available to the | ||
current user. Once you created the storage, you can run the tests | ||
quickly as yourself directly from your editor. | ||
|
||
|
||
## Installing | ||
|
||
Currently the only way to install this is cloning the repo and | ||
installing the package manually. We will make this available via pip | ||
soon. | ||
|
||
|
||
## Creating configuration file | ||
|
||
The userstorage tool creates storage based on configuration file that | ||
you must provide. | ||
|
||
The configuration module is use both by the userstorage tool to | ||
provision the storage, and by the tests consuming the storage. | ||
|
||
The configuration module typically starts by importing the backends you | ||
want to provision: | ||
|
||
from userstorage import File | ||
|
||
The configuration module must define these names: | ||
|
||
# Where storage is provisioned. | ||
BASE_DIR = "/path/to/my/storage" | ||
|
||
# Storage configurations needed by the tests. | ||
BACKENDS = {} | ||
|
||
See exampleconf.py for example configuration used by the tests for this | ||
project. | ||
|
||
|
||
## Setting up user storage | ||
|
||
To setup storage for these tests, run: | ||
|
||
python -m userstorage setup exampleconf.py | ||
|
||
This can be run once when setting up development environment, and must | ||
be run again after rebooting the host. | ||
|
||
If you want to tear down the user storage, run: | ||
|
||
python -m userstorage teardown exampleconf.py | ||
|
||
There is no need to tear down the storage normally. The loop devices are | ||
backed up by sparse files and do not consume much resources. | ||
|
||
|
||
## Consuming the storage in your tests | ||
|
||
See test/userstorage_test.py for example test module consuming storage | ||
set up by userstorage tool, and the exampleconf.py module. | ||
|
||
Note that some storage may not be available on some systems. Your tests | ||
can check if a storage is available and skip or mark the test as xfail | ||
if needed. | ||
|
||
|
||
## How it works? | ||
|
||
The userstorage tool creates this directory layout in the BASE_DIR | ||
defined in the configuration module: | ||
|
||
$ tree /var/tmp/example-storage/ | ||
/var/tmp/example-storage/ | ||
├── block-4k-backing | ||
├── block-4k-loop -> /dev/loop2 | ||
├── block-512-backing | ||
├── block-512-loop -> /dev/loop3 | ||
├── file-4k-backing | ||
├── file-4k-loop -> /dev/loop4 | ||
├── file-4k-mount | ||
│ ├── file | ||
│ └── lost+found [error opening dir] | ||
├── file-512-backing | ||
├── file-512-loop -> /dev/loop5 | ||
└── file-512-mount | ||
├── file | ||
└── lost+found [error opening dir] | ||
|
||
The symbolic links (e.g. file-4k-loop) link to the loop devices created | ||
by the tool (/dev/loop4), and used to tear down the storage. | ||
|
||
The actual file used for the tests are created inside the mounted | ||
filesystem (/var/tmp/example-storage/file-4k-mount/file). | ||
|
||
|
||
## Projects using userstorage | ||
|
||
- sanlock - using very early version of this tool | ||
- vdsm - using more recent version of this tool | ||
|
||
(Please add your project here) | ||
|
||
|
||
## Contributing | ||
|
||
If you found a bug, please open an issue. | ||
|
||
If you have an idea for improving this tool, please open an issue to | ||
discuss the idea. | ||
|
||
For trivial changes please send a pull request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
""" | ||
Example userstorage configuration module. | ||
""" | ||
|
||
from userstorage import LoopDevice, Mount, File | ||
|
||
GiB = 1024**3 | ||
|
||
# This is the directory where backing files, symlinks to loop devices, and | ||
# mount directories are created. | ||
|
||
BASE_DIR = "/var/tmp/example-storage" | ||
|
||
|
||
# Dictionary of backends. Here is an example configuration providing file and | ||
# block storage with 512 and 4k sector size. Note that 4k storage is defined as | ||
# optional since creating loop device with 4k storgae is not supported on all | ||
# environments and may be flaky in some supported environments. | ||
|
||
BACKENDS = { | ||
|
||
"block-512": LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="block-512", | ||
size=GiB, | ||
sector_size=512, | ||
), | ||
|
||
"block-4k": LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="block-4k", | ||
size=GiB, | ||
sector_size=4096, | ||
required=False, | ||
), | ||
|
||
"mount-512": Mount( | ||
LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="mount-512", | ||
size=GiB, | ||
sector_size=512, | ||
) | ||
), | ||
|
||
"mount-4k": Mount( | ||
LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="mount-4k", | ||
size=GiB, | ||
sector_size=4096, | ||
required=False, | ||
) | ||
), | ||
|
||
"file-512": File( | ||
Mount( | ||
LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="file-512", | ||
size=GiB, | ||
sector_size=512, | ||
) | ||
) | ||
), | ||
|
||
"file-4k": File( | ||
Mount( | ||
LoopDevice( | ||
base_dir=BASE_DIR, | ||
name="file-4k", | ||
size=GiB, | ||
sector_size=4096, | ||
required=False, | ||
) | ||
) | ||
), | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# Copyright (C) 2019 Nir Soffer | ||
# This program is free software; see LICENSE for more info. | ||
|
||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
import errno | ||
import glob | ||
import io | ||
import mmap | ||
import os | ||
import shutil | ||
import stat | ||
import subprocess | ||
|
||
from contextlib import closing | ||
|
||
import pytest | ||
|
||
import userstorage | ||
|
||
BACKENDS = userstorage.load_config("exampleconf.py").BACKENDS | ||
|
||
|
||
@pytest.fixture( | ||
params=[ | ||
BACKENDS["block-512"], | ||
BACKENDS["block-4k"], | ||
], | ||
ids=str, | ||
) | ||
def user_loop(request): | ||
backend = validate_backend(request.param) | ||
yield backend | ||
|
||
# Discard loop device to ensure next test is not affected. | ||
# TODO: Should be implemented by backend. | ||
|
||
subprocess.check_output(["blkdiscard", backend.path]) | ||
|
||
|
||
@pytest.fixture( | ||
params=[ | ||
BACKENDS["mount-512"], | ||
BACKENDS["mount-4k"] | ||
], | ||
ids=str, | ||
) | ||
def user_mount(request): | ||
backend = validate_backend(request.param) | ||
yield backend | ||
|
||
# Remove files and directories created by the current tests to ensure that | ||
# the next test is not affected. | ||
# TODO: Should be implemented by backend. | ||
|
||
for path in glob.glob(os.path.join(backend.path, "*")): | ||
if os.path.isdir(path): | ||
shutil.rmtree(path) | ||
else: | ||
os.remove(path) | ||
|
||
|
||
@pytest.fixture( | ||
params=[ | ||
BACKENDS["file-512"], | ||
BACKENDS["file-4k"], | ||
], | ||
ids=str, | ||
) | ||
def user_file(request): | ||
backend = validate_backend(request.param) | ||
yield backend | ||
|
||
# Truncate file to ensure that next test is not affected. | ||
# TODO: Should be implemented by backend. | ||
|
||
with open(backend.path, "w") as f: | ||
f.truncate(0) | ||
|
||
|
||
def validate_backend(backend): | ||
if not backend.exists(): | ||
pytest.xfail("backend {} not available".format(backend)) | ||
return backend | ||
|
||
|
||
def test_loop_device(user_loop): | ||
assert is_block_device(user_loop.path) | ||
assert logical_block_size(user_loop.path) == user_loop.sector_size | ||
|
||
|
||
def test_mount(user_mount): | ||
assert os.path.isdir(user_mount.path) | ||
|
||
filename = os.path.join(user_mount.path, "file") | ||
with open(filename, "w") as f: | ||
f.truncate(4096) | ||
|
||
assert detect_block_size(filename) == user_mount.sector_size | ||
|
||
|
||
def test_file(user_file): | ||
assert os.path.isfile(user_file.path) | ||
assert detect_block_size(user_file.path) == user_file.sector_size | ||
|
||
|
||
def is_block_device(path): | ||
mode = os.stat(path).st_mode | ||
return stat.S_ISBLK(mode) | ||
|
||
|
||
def logical_block_size(path): | ||
realpath = os.path.realpath(path) | ||
dev = os.path.split(realpath)[1] | ||
lbs = "/sys/block/{}/queue/logical_block_size".format(dev) | ||
with open(lbs) as f: | ||
return int(f.readline()) | ||
|
||
|
||
def detect_block_size(path): | ||
""" | ||
Detect the minimal block size for direct I/O. This is typically the sector | ||
size of the underlying storage. | ||
Copied from ovirt-imageio. | ||
""" | ||
fd = os.open(path, os.O_RDONLY | os.O_DIRECT) | ||
with io.FileIO(fd, "r") as f: | ||
for block_size in (512, 4096): | ||
buf = mmap.mmap(-1, block_size) | ||
with closing(buf): | ||
try: | ||
f.readinto(buf) | ||
except EnvironmentError as e: | ||
if e.errno != errno.EINVAL: | ||
raise | ||
else: | ||
return block_size | ||
raise RuntimeError("Cannot detect block size") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[tox] | ||
envlist = py27,py36,py37,flake8,pylint | ||
skip_missing_interpreters = True | ||
skipsdist = True | ||
usedevelop = True | ||
|
||
[testenv] | ||
deps = | ||
pytest | ||
commands = | ||
py.test {posargs} | ||
|
||
[testenv:flake8] | ||
deps = | ||
flake8 | ||
commands = | ||
flake8 | ||
|
||
[testenv:pylint] | ||
deps = | ||
pylint | ||
commands = | ||
pylint -E userstorage | ||
|
||
[pytest] | ||
addopts = -v -rxXs --durations=10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright (C) 2019 Nir Soffer | ||
# This program is free software; see LICENSE for more info. | ||
|
||
# flake8: noqa | ||
|
||
# Backends. | ||
from userstorage.loop import LoopDevice | ||
from userstorage.mount import Mount | ||
from userstorage.file import File | ||
|
||
# Helpers. | ||
from userstorage.config import load_config |
Oops, something went wrong.