Skip to content

Commit

Permalink
breaking change: add sequence number to naming
Browse files Browse the repository at this point in the history
this is for forward compatibility with future support for very large
backup streams.
  • Loading branch information
sbrudenell committed Aug 29, 2024
1 parent 31893f7 commit 4973d42
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 17 deletions.
9 changes: 9 additions & 0 deletions src/btrfs2s3/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
_SNDP = "sndp"
_PRNT = "prnt"
_MDVN = "mdvn"
_SEQN = "seqn"

_METADATA_VERSION = 1

Expand Down Expand Up @@ -93,6 +94,7 @@ def get_path_suffixes(self, *, tzinfo: tzinfo | str | None = None) -> Sequence[s
f".{_SNDP}{send_parent_uuid}",
f".{_PRNT}{parent_uuid}",
f".{_MDVN}{_METADATA_VERSION}",
f".{_SEQN}0",
]

@classmethod
Expand Down Expand Up @@ -122,6 +124,7 @@ def from_path(cls, path: str) -> Self: # noqa: C901
ctransid: int | None = None
ctime: Arrow | None = None
version: int | None = None
sequence_number: int | None = None

suffixes = pathlib.PurePath(path).suffixes
for suffix in suffixes:
Expand All @@ -144,13 +147,19 @@ def from_path(cls, path: str) -> Self: # noqa: C901
elif code == _MDVN:
with suppress(ValueError):
version = int(rest)
elif code == _SEQN:
with suppress(ValueError):
sequence_number = int(rest)

if version is None:
msg = "backup name metadata version missing (not a backup?)"
raise ValueError(msg)
if version != _METADATA_VERSION:
msg = "unsupported backup name metadata version"
raise ValueError(msg)
if sequence_number != 0:
msg = "unsupported sequence number"
raise ValueError(msg)
if (
uuid is None
or parent_uuid is None
Expand Down
77 changes: 60 additions & 17 deletions tests/backups/path_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import itertools
import random
import re
from typing import TYPE_CHECKING
from uuid import UUID
Expand All @@ -11,6 +12,9 @@

if TYPE_CHECKING:
from typing import Sequence
from typing import TypeVar

_T = TypeVar("_T")


def test_get_path_suffixes_with_real_timezone() -> None:
Expand All @@ -29,6 +33,7 @@ def test_get_path_suffixes_with_real_timezone() -> None:
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".mdvn1",
".seqn0",
]
assert got == expected

Expand All @@ -52,6 +57,7 @@ def test_get_path_suffixes_default_to_utc() -> None:
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".mdvn1",
".seqn0",
]
assert got == expected

Expand All @@ -75,25 +81,41 @@ def test_get_path_suffixes_with_full_backup() -> None:
".sndp00000000-0000-0000-0000-000000000000",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".mdvn1",
".seqn0",
]
assert got == expected

round_trip = BackupInfo.from_path(f"name{''.join(got)}.gz")
assert round_trip == info


def _fixed_choices(population: Sequence[_T], seed: int, k: int) -> list[_T]:
state = random.getstate()
random.seed(seed)
result = random.choices(population, k=k)
random.setstate(state)
return result


@pytest.mark.parametrize(
"suffixes",
itertools.permutations(
[
".ctim2006-01-01T00:00:00-08:00",
".ctid12345",
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d",
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".mdvn1",
".gz",
]
_fixed_choices(
list(
itertools.permutations(
[
".ctim2006-01-01T00:00:00-08:00",
".ctid12345",
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d",
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".mdvn1",
".seqn0",
".gz",
]
)
),
0,
1000,
),
)
def test_from_path_with_suffixes_in_any_order(suffixes: Sequence[str]) -> None:
Expand All @@ -117,30 +139,35 @@ def test_from_path_with_suffixes_in_any_order(suffixes: Sequence[str]) -> None:
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1",
".mdvn1"
".seqn0",
".ctim2006-01-01T00:00:00-08:00"
".ctid12345"
".uuid3fd11d8e-811O-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1",
".mdvn1"
".seqn0",
".ctim2006-01-01T00:00:00-08:00"
".ctidl2345.u3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1",
".mdvn1"
".seqn0",
".ctim2006-01-01T00:00:00-08:00"
".ctid12345"
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1",
".mdvn1"
".seqn0",
".ctim2006-01-01T00:00:00-08:00"
".ctid12345"
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3gcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1",
".mdvn1"
".seqn0",
],
)
def test_bad_paths(bad_path: str) -> None:
Expand All @@ -159,13 +186,15 @@ def test_bad_paths(bad_path: str) -> None:
".ctid12345"
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e",
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".seqn0",
".ctim2006-01-01T00:00:00-08:00"
".ctid12345"
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvnI",
".mdvnI"
".seqn0",
],
)
def test_no_version(bad_path: str) -> None:
Expand All @@ -185,4 +214,18 @@ def test_bad_version() -> None:
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1000"
".seqn0"
)


def test_bad_sequence_number() -> None:
with pytest.raises(ValueError, match="unsupported sequence number"):
BackupInfo.from_path(
".ctim2006-01-01T00:00:00-08:00"
".ctid12345"
".uuid3fd11d8e-8110-4cd0-b85c-bae3dda86a3d"
".sndp3ae01eae-d50d-4187-b67f-cef0ef973e1f"
".prnt9d9d3bcb-4b62-46a3-b6e2-678eeb24f54e"
".mdvn1"
".seqn1"
)

0 comments on commit 4973d42

Please sign in to comment.