-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Abstracted out create and delete operations into SnapMechanism class
Issue #32
- Loading branch information
Showing
10 changed files
with
211 additions
and
72 deletions.
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
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
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
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
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
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
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,71 @@ | ||
# Copyright 2022 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import json | ||
import os | ||
import tempfile | ||
import unittest | ||
from unittest import mock | ||
|
||
from . import snap_holder | ||
from . import snap_mechanisms | ||
|
||
# For testing, we can access private methods. | ||
# pyright: reportPrivateUsage=false | ||
|
||
|
||
class SnapHolderTest(unittest.TestCase): | ||
def test_create_and_delete(self): | ||
with tempfile.TemporaryDirectory() as dir: | ||
snap_destination = os.path.join(dir, "root-20231122193630") | ||
snap = snap_holder.Snapshot(snap_destination) | ||
self.assertEqual(snap._snap_type, snap_mechanisms.SnapType.UNKNOWN) | ||
|
||
with mock.patch.object( | ||
snap_mechanisms._BtrfsSnapMechanism, "verify_volume", return_value=True | ||
) as mock_verify_volume, mock.patch.object( | ||
snap_mechanisms._BtrfsSnapMechanism, "create", return_value=None | ||
) as mock_create: | ||
snap.create_from(snap_mechanisms.SnapType.BTRFS, "parent") | ||
mock_verify_volume.assert_called_once_with("parent") | ||
mock_create.assert_called_once_with("parent", snap_destination) | ||
|
||
snap2 = snap_holder.Snapshot(snap_destination) | ||
self.assertEqual(snap2._snap_type, snap_mechanisms.SnapType.BTRFS) | ||
|
||
with open(f"{snap_destination}-meta.json") as f: | ||
self.assertEqual( | ||
json.load(f), {"snap_type": "BTRFS", "source": "parent"} | ||
) | ||
|
||
with mock.patch.object( | ||
snap_mechanisms._BtrfsSnapMechanism, "delete", return_value=None | ||
) as mock_delete: | ||
snap2.delete() | ||
mock_delete.assert_called_once_with(snap_destination) | ||
self.assertFalse(os.path.exists(f"{snap_destination}-meta.json")) | ||
|
||
def test_backcompat(self): | ||
with tempfile.TemporaryDirectory() as dir: | ||
snap_destination = os.path.join(dir, "root-20231122193630") | ||
with open(f"{snap_destination}-meta.json", "w") as f: | ||
json.dump({"source": "parent"}, f) | ||
snap = snap_holder.Snapshot(snap_destination) | ||
|
||
# Without any snap_type, defaults to BTRFS to continue working with old snaps. | ||
self.assertEqual(snap._snap_type, snap_mechanisms.SnapType.BTRFS) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
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,29 +1,97 @@ | ||
import abc | ||
import enum | ||
import functools | ||
import logging | ||
|
||
from . import global_flags | ||
from . import os_utils | ||
|
||
# TODO: Move btrfs based rollback to this abstraction. | ||
# TODO: Move btrfs sync to this abstraction. | ||
|
||
# The type of snapshot is maintained in two places - | ||
# 1. Config | ||
# 2. Snapshot | ||
class SnapType(enum.Enum): | ||
UNKNOWN = "UNKNOWN" | ||
BTRFS = "BTRFS" | ||
|
||
|
||
class _BtrfsSnapshotter: | ||
@staticmethod | ||
def sync(mount_paths: set[str]) -> None: | ||
for mount_path in sorted(mount_paths): | ||
if global_flags.FLAGS.dryrun: | ||
os_utils.eprint(f"Would sync {mount_path}") | ||
continue | ||
os_utils.eprint("Syncing ...", flush=True) | ||
os_utils.execute_sh(f"btrfs subvolume sync {mount_path}") | ||
class SnapMechanism(abc.ABC): | ||
"""Interface with necessary methods to implement snapshotting system. | ||
Example implementations may be based on btrfs, rsync, bcachefs, etc. | ||
""" | ||
@abc.abstractmethod | ||
def verify_volume(self, mount_point: str) -> bool: | ||
pass | ||
|
||
@abc.abstractmethod | ||
def create(self, source: str, destination: str): | ||
pass | ||
|
||
@abc.abstractmethod | ||
def delete(self, destination: str): | ||
pass | ||
|
||
|
||
def _execute_sh(cmd: str): | ||
if global_flags.FLAGS.dryrun: | ||
os_utils.eprint("Would run " + cmd) | ||
else: | ||
os_utils.execute_sh(cmd) | ||
|
||
|
||
class _BtrfsSnapMechanism(SnapMechanism): | ||
# @staticmethod | ||
# def sync(mount_paths: set[str]) -> None: | ||
# for mount_path in sorted(mount_paths): | ||
# if global_flags.FLAGS.dryrun: | ||
# os_utils.eprint(f"Would sync {mount_path}") | ||
# continue | ||
# os_utils.eprint("Syncing ...", flush=True) | ||
# os_utils.execute_sh(f"btrfs subvolume sync {mount_path}") | ||
|
||
def verify_volume(self, mount_point: str) -> bool: | ||
# Based on https://stackoverflow.com/a/32865333/196462 | ||
fstype = os_utils.execute_sh( | ||
"stat -f --format=%T " + mount_point, error_ok=True | ||
) | ||
if not fstype: | ||
logging.warning(f"Not btrfs (cannot determine filesystem): {mount_point}") | ||
return False | ||
if fstype.strip() != "btrfs": | ||
logging.warning(f"Not btrfs (filesystem not btrfs): {mount_point}") | ||
return False | ||
inodenum = os_utils.execute_sh("stat --format=%i " + mount_point, error_ok=True) | ||
if not inodenum: | ||
logging.warning(f"Not btrfs (cannot determine inode): {mount_point}") | ||
return False | ||
if inodenum.strip() != "256": | ||
logging.warning( | ||
f"Not btrfs (inode not 256, possibly a subdirectory of a btrfs mount): {mount_point}" | ||
) | ||
return False | ||
return True | ||
|
||
def create(self, source: str, destination: str): | ||
try: | ||
_execute_sh("btrfs subvolume snapshot -r " f"{source} {destination}") | ||
except os_utils.CommandError: | ||
logging.error("Unable to create; are you running as root?") | ||
raise | ||
|
||
def delete(self, destination: str): | ||
try: | ||
_execute_sh(f"btrfs subvolume delete {destination}") | ||
except os_utils.CommandError: | ||
logging.error("Unable to delete; are you running as root?") | ||
raise | ||
|
||
|
||
@functools.cache | ||
def get() -> _BtrfsSnapshotter: | ||
def get(snap_type: SnapType) -> SnapMechanism: | ||
"""Singleton implementation.""" | ||
return _BtrfsSnapshotter() | ||
if snap_type == SnapType.BTRFS: | ||
return _BtrfsSnapMechanism() | ||
raise RuntimeError(f"Unknown snap_type {snap_type}") |
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
Oops, something went wrong.