commit b8edec0076857728b998b895f59bd2f73b45f692 Author: Matthias Blankertz Date: Wed Sep 29 00:09:57 2021 +0200 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..38db8dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Matthias Blankertz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0f08daa --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-dateutil diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ed048af --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, find_packages + +with open('LICENSE') as f: + license = f.read() + +setup( + name='zfs_smart_snapshot', + version='0.0.0', + description='Smart snapshoting for ZFS', + author='Matthias Blankertz', + author_email='matthias@blankertz.org', + license=license, + packages=find_packages(exclude=('tests', 'docs')), + entry_points={ + 'console_scripts': [ + 'zfs_smart_snapshot = zfs_smart_snapshot.main:main', + ], + }, +) diff --git a/tests/context.py b/tests/context.py new file mode 100644 index 0000000..60c19a1 --- /dev/null +++ b/tests/context.py @@ -0,0 +1,5 @@ +import os +import sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import zfs_smart_snapshot diff --git a/tests/test_keep.py b/tests/test_keep.py new file mode 100644 index 0000000..0ec3c6f --- /dev/null +++ b/tests/test_keep.py @@ -0,0 +1,39 @@ +from datetime import datetime +from zfs_smart_snapshot.main import keep_year, keep_month, keep_day, keep_hour, keep_frequent + + +dates = [datetime(2018, 2, 3), datetime(2019, 3, 4), datetime(2020, 4, 5), datetime(2021, 1, 2), + datetime(2021, 2, 2), datetime(2021, 2, 3), datetime(2021, 3, 3), datetime(2021, 3, 4), + datetime(2021, 3, 5)] +now = datetime(2021, 3, 5, 22, 45, 0) + + +def test_keep_year(): + assert keep_year(now, dates, 2) == {datetime(2020, 4, 5), datetime(2021, 1, 2)} + assert keep_year(now, dates, 0) == set() + dates_gap = [datetime(2018, 2, 3), datetime(2019, 3, 4), datetime(2021, 5, 6)] + assert keep_year(datetime(2021, 5, 6), dates_gap, 3) == {datetime(2019, 3, 4), datetime(2021, 5, 6)} + assert keep_year(datetime(2022, 2, 3), dates_gap, 1) == set() + + +def test_keep_month(): + assert keep_month(now, dates, 3) == {datetime(2021, 1, 2), datetime(2021, 2, 2), datetime(2021, 3, 3)} + + +def test_keep_day(): + assert keep_day(now, dates, 3) == {datetime(2021, 3, 3), datetime(2021, 3, 4), datetime(2021, 3, 5)} + + +datetimes = [datetime(2021, 3, 5, 17, 0, 0), datetime(2021, 3, 5, 18, 0, 0), datetime(2021, 3, 5, 19, 0, 0), + datetime(2021, 3, 5, 20, 0, 0), datetime(2021, 3, 5, 21, 0, 0), datetime(2021, 3, 5, 22, 0, 0), + datetime(2021, 3, 5, 22, 15, 0), datetime(2021, 3, 5, 22, 30, 0), datetime(2021, 3, 5, 22, 45, 0)] + + +def test_keep_hour(): + assert keep_hour(now, datetimes, 3) == {datetime(2021, 3, 5, 20, 0, 0), datetime(2021, 3, 5, 21, 0, 0), + datetime(2021, 3, 5, 22, 0, 0)} + + +def test_keep_frequent(): + assert keep_frequent(now, datetimes, 3) == {datetime(2021, 3, 5, 22, 15, 0), datetime(2021, 3, 5, 22, 30, 0), + datetime(2021, 3, 5, 22, 45, 0)} diff --git a/zfs_smart_snapshot/__init__.py b/zfs_smart_snapshot/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/zfs_smart_snapshot/__init__.py @@ -0,0 +1 @@ + diff --git a/zfs_smart_snapshot/main.py b/zfs_smart_snapshot/main.py new file mode 100644 index 0000000..4d1db0d --- /dev/null +++ b/zfs_smart_snapshot/main.py @@ -0,0 +1,65 @@ +from datetime import datetime +from dateutil.relativedelta import relativedelta + + +def keep_date(now, snapshots, date_convert, delta, count): + keep = set() + if count < 1: + return keep + start_date = date_convert(now) - delta*(count-1) + for snapshot in snapshots: + if snapshot.date() >= start_date: + keep.add(snapshot) + start_date = date_convert(snapshot) + delta + if start_date > now.date(): + break + return keep + + +def keep_year(now, snapshots, count): + return keep_date(now, snapshots, lambda d: d.date().replace(day=1, month=1), relativedelta(years=1), count) + + +def keep_month(now, snapshots, count): + return keep_date(now, snapshots, lambda d: d.date().replace(day=1), relativedelta(months=1), count) + + +def keep_day(now, snapshots, count): + return keep_date(now, snapshots, lambda d: d.date(), relativedelta(days=1), count) + + +def keep_time(now, snapshots, convert, delta, count): + keep = set() + if count < 1: + return keep + start = convert(now) - delta*(count-1) + for snapshot in snapshots: + if snapshot >= start: + keep.add(snapshot) + start = convert(snapshot) + delta + if start > now: + break + return keep + + +def keep_hour(now, snapshots, count): + return keep_time(now, snapshots, lambda d: d.replace(minute=0, second=0, microsecond=0), relativedelta(hours=1), + count) + + +def keep_frequent(now, snapshots, count): + return keep_time(now, snapshots, lambda d: d.replace(minute=d.minute//15*15, second=0, microsecond=0), + relativedelta(minutes=15), count) + + +def snapshots_to_keep(now, snapshots, yearly=0, monthly=0, weekly=0, daily=0, hourly=0, frequently=0): + snapshots.sort() + keep = set() + keep.union(keep_year(now, snapshots, yearly)) + keep.union(keep_month(now, snapshots, monthly)) + keep.union(keep_day(now, snapshots, daily)) + return keep + + +def main(): + print("Hello, world!")