diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 398ff08..0000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -branch = True diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 4a290ff..0389f2a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -7,7 +7,6 @@ on: push: branches: [ main ] pull_request: - branches: [ main ] jobs: check-poetry: diff --git a/changelog.d/.gitkeep b/changelog.d/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/changelog.d/20250611_120003_jb_PL_133453_rbd_whole_object.rst b/changelog.d/20250611_120003_jb_PL_133453_rbd_whole_object.rst new file mode 100644 index 0000000..e09f7ac --- /dev/null +++ b/changelog.d/20250611_120003_jb_PL_133453_rbd_whole_object.rst @@ -0,0 +1,3 @@ +.. A new scriv changelog fragment. + +- Fix whole object detection (PL-133453) diff --git a/poetry.lock b/poetry.lock index 8f970e0..b812bc0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -771,21 +771,21 @@ pytest = ">=2.2" [[package]] name = "pytest-cov" -version = "4.1.0" +version = "6.1.1" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, + {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-flake8" @@ -1065,4 +1065,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "666d6cd72e7e3de3d31bd41749548da77fb8d39aa99aca4092f75c919c5d129f" +content-hash = "069ccfd6291927d495545f080eb094b1105be2701d67a78feed07e0cf657f608" diff --git a/pyproject.toml b/pyproject.toml index c26edd7..a689793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,18 @@ version = "literal: pyproject.toml: tool.poetry.version" entry_title_template = "{% if version %}{{ version }} {% endif %}({{ date.strftime('%Y-%m-%d') }})" categories = "" + +[tool.pytest.ini_options] +addopts = "--timeout=30 --tb=native --cov=src --cov-report=html --cov-config=pyproject.toml src -r w" +markers = "slow: This is a non-unit test and thus is not run by default. Use ``-m slow`` to run these, or ``-m 1`` to run all tests." +log_level = "NOTSET" +asyncio_mode = "auto" +filterwarnings = [ "ignore::DeprecationWarning:telnetlib3.*:" ] + +[tool.coverage.run] +branch = true +omit = [ "*/tests/*" ] + [tool.poetry] name = "backy" version = "2.5.4.dev0" @@ -59,7 +71,7 @@ pre-commit = "^3.3.3" pytest = "^7.4.0" pytest-asyncio = "^0.21.1" pytest-cache = "^1.0" -pytest-cov = "^4.1.0" +pytest-cov = "^6.1.0" pytest-flake8 = "^1.1.1" pytest-timeout = "^2.1.0" scriv = "^1.3.1" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index d3c553f..0000000 --- a/pytest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[pytest] -addopts = --timeout=30 --tb=native --cov=src --cov-report=html src -r w -markers = slow: This is a non-unit test and thus is not run by default. Use ``-m slow`` to run these, or ``-m 1`` to run all tests. -log_level = NOTSET -asyncio_mode = auto - - -filterwarnings = - ignore::DeprecationWarning:telnetlib3.*: diff --git a/src/backy/sources/ceph/__init__.py b/src/backy/sources/ceph/__init__.py index 623db0e..91b1aa4 100644 --- a/src/backy/sources/ceph/__init__.py +++ b/src/backy/sources/ceph/__init__.py @@ -1,14 +1 @@ -from subprocess import PIPE, run - - -def detect_whole_object_support(): - result = run( - ["rbd", "help", "export-diff"], stdout=PIPE, stderr=PIPE, check=True - ) - return "--whole-object" in result.stdout.decode("ascii") - - -try: - CEPH_RBD_SUPPORTS_WHOLE_OBJECT_DIFF = detect_whole_object_support() -except Exception: - CEPH_RBD_SUPPORTS_WHOLE_OBJECT_DIFF = False +# Make this a package. diff --git a/src/backy/sources/ceph/rbd.py b/src/backy/sources/ceph/rbd.py index 9186371..7bd74a0 100644 --- a/src/backy/sources/ceph/rbd.py +++ b/src/backy/sources/ceph/rbd.py @@ -1,12 +1,11 @@ import contextlib +import functools import json import subprocess from typing import IO, Iterator, cast from structlog.stdlib import BoundLogger -import backy.sources.ceph - from ...ext_deps import RBD from ...utils import CHUNK_SIZE from .diff import RBDDiffV1 @@ -66,6 +65,10 @@ def _rbd_stream(self, cmd: list[str]) -> Iterator[IO[bytes]]: if rc: raise subprocess.CalledProcessError(rc, proc.args) + @functools.cached_property + def _supports_whole_object(self): + return "--whole-object" in self._rbd(["help", "export-diff"]) + def exists(self, snapspec): try: return self._rbd(["info", snapspec], format="json") @@ -134,7 +137,7 @@ def snap_rm(self, image): @contextlib.contextmanager def export_diff(self, new: str, old: str) -> Iterator[RBDDiffV1]: - if backy.sources.ceph.CEPH_RBD_SUPPORTS_WHOLE_OBJECT_DIFF: + if self._supports_whole_object: EXPORT_WHOLE_OBJECT = ["--whole-object"] else: EXPORT_WHOLE_OBJECT = [] diff --git a/src/backy/sources/ceph/tests/conftest.py b/src/backy/sources/ceph/tests/conftest.py index befa272..f1190de 100644 --- a/src/backy/sources/ceph/tests/conftest.py +++ b/src/backy/sources/ceph/tests/conftest.py @@ -4,7 +4,6 @@ import pytest -import backy.sources.ceph from backy.sources.ceph.rbd import RBDClient @@ -242,11 +241,8 @@ def unmap(self, device): @pytest.fixture(params=[CephJewelCLI, CephLuminousCLI, CephNautilusCLI]) def rbdclient(request, tmpdir, monkeypatch, log): - monkeypatch.setattr( - backy.sources.ceph, "CEPH_RBD_SUPPORTS_WHOLE_OBJECT_DIFF", True - ) - client = RBDClient(log) + client._supports_whole_object = True client._ceph_cli = request.param(tmpdir) return client