Compare commits
7 Commits
v3.0.1-bet
...
v3.0.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| 628cd75941 | |||
| 1da14c5c3b | |||
| c83d0fcff2 | |||
| 573af341b8 | |||
| a64168bee2 | |||
| c678ae5f9a | |||
| e95967db53 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,4 +11,3 @@ __pycache__
|
|||||||
python2.env
|
python2.env
|
||||||
venv
|
venv
|
||||||
.idea
|
.idea
|
||||||
OQ
|
|
||||||
11
README.md
11
README.md
@ -148,6 +148,8 @@ rpool/swap autobackup:offsite1 true
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
ZFS properties are ```inherited``` by child datasets. Since we've set the property on the highest dataset, we're essentially backupping the whole pool.
|
||||||
|
|
||||||
Because we don't want to backup everything, we can exclude certain filesystem by setting the property to false:
|
Because we don't want to backup everything, we can exclude certain filesystem by setting the property to false:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@ -163,6 +165,13 @@ rpool/swap autobackup:offsite1 false
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The autobackup-property can have 3 values:
|
||||||
|
* ```true```: Backup the dataset and all its children
|
||||||
|
* ```false```: Dont backup the dataset and all its children. (used to exclude certain datasets)
|
||||||
|
* ```child```: Only backup the children off the dataset, not the dataset itself.
|
||||||
|
|
||||||
|
Only use the zfs-command to set these properties, not the zpool command.
|
||||||
|
|
||||||
### Running zfs-autobackup
|
### Running zfs-autobackup
|
||||||
|
|
||||||
Run the script on the backup server and pull the data from the server specified by --ssh-source.
|
Run the script on the backup server and pull the data from the server specified by --ssh-source.
|
||||||
@ -654,7 +663,7 @@ for HOST in $HOSTS; do
|
|||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
|
||||||
This script will also send the backup status to Zabbix. (if you've installed my zabbix-job-status script)
|
This script will also send the backup status to Zabbix. (if you've installed my zabbix-job-status script https://github.com/psy0rz/stuff/tree/master/zabbix-jobs)
|
||||||
|
|
||||||
# Sponsor list
|
# Sponsor list
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from basetest import *
|
from basetest import *
|
||||||
|
|
||||||
|
|
||||||
class TestZfsNode(unittest2.TestCase):
|
class TestExternalFailures(unittest2.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
prepare_zpools()
|
prepare_zpools()
|
||||||
@ -259,8 +259,28 @@ test_target1/test_source2/fs2/sub@test-20101111000002
|
|||||||
with patch('time.strftime', return_value="20101111000001"):
|
with patch('time.strftime', return_value="20101111000001"):
|
||||||
self.assertTrue(ZfsAutobackup("test test_target1 --verbose --allow-empty".split(" ")).run())
|
self.assertTrue(ZfsAutobackup("test test_target1 --verbose --allow-empty".split(" ")).run())
|
||||||
|
|
||||||
############# TODO:
|
#UPDATE: offcourse the one thing that wasn't tested had a bug :( (in ExecuteNode.run()).
|
||||||
def test_ignoretransfererrors(self):
|
def test_ignoretransfererrors(self):
|
||||||
|
|
||||||
self.skipTest(
|
self.skipTest("Not sure how to implement a test for this without some serious hacking and patching.")
|
||||||
"todo: create some kind of situation where zfs recv exits with an error but transfer is still ok (happens in practice with acltype)")
|
|
||||||
|
# #recreate target pool without any features
|
||||||
|
# # shelltest("zfs set compress=on test_source1; zpool destroy test_target1; zpool create test_target1 -o feature@project_quota=disabled /dev/ram2")
|
||||||
|
#
|
||||||
|
# with patch('time.strftime', return_value="20101111000000"):
|
||||||
|
# self.assertFalse(ZfsAutobackup("test test_target1 --verbose --allow-empty --no-progress".split(" ")).run())
|
||||||
|
#
|
||||||
|
# r = shelltest("zfs list -H -o name -r -t all test_target1")
|
||||||
|
#
|
||||||
|
# self.assertMultiLineEqual(r, """
|
||||||
|
# test_target1
|
||||||
|
# test_target1/test_source1
|
||||||
|
# test_target1/test_source1/fs1
|
||||||
|
# test_target1/test_source1/fs1@test-20101111000002
|
||||||
|
# test_target1/test_source1/fs1/sub
|
||||||
|
# test_target1/test_source1/fs1/sub@test-20101111000002
|
||||||
|
# test_target1/test_source2
|
||||||
|
# test_target1/test_source2/fs2
|
||||||
|
# test_target1/test_source2/fs2/sub
|
||||||
|
# test_target1/test_source2/fs2/sub@test-20101111000002
|
||||||
|
# """)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import subprocess
|
|||||||
|
|
||||||
from zfs_autobackup.LogStub import LogStub
|
from zfs_autobackup.LogStub import LogStub
|
||||||
|
|
||||||
|
|
||||||
class ExecuteNode(LogStub):
|
class ExecuteNode(LogStub):
|
||||||
"""an endpoint to execute local or remote commands via ssh"""
|
"""an endpoint to execute local or remote commands via ssh"""
|
||||||
|
|
||||||
@ -46,17 +47,23 @@ class ExecuteNode(LogStub):
|
|||||||
|
|
||||||
def run(self, cmd, inp=None, tab_split=False, valid_exitcodes=None, readonly=False, hide_errors=False, pipe=False,
|
def run(self, cmd, inp=None, tab_split=False, valid_exitcodes=None, readonly=False, hide_errors=False, pipe=False,
|
||||||
return_stderr=False):
|
return_stderr=False):
|
||||||
"""run a command on the node cmd: the actual command, should be a list, where the first item is the command
|
"""run a command on the node.
|
||||||
and the rest are parameters. input: Can be None, a string or a pipe-handle you got from another run()
|
|
||||||
tab_split: split tabbed files in output into a list valid_exitcodes: list of valid exit codes for this
|
:param cmd: the actual command, should be a list, where the first item is the command
|
||||||
command (checks exit code of both sides of a pipe) readonly: make this True if the command doesn't make any
|
and the rest are parameters.
|
||||||
changes and is safe to execute in testmode hide_errors: don't show stderr output as error, instead show it as
|
:param inp: Can be None, a string or a pipe-handle you got from another run()
|
||||||
debugging output (use to hide expected errors) pipe: Instead of executing, return a pipe-handle to be used to
|
:param tab_split: split tabbed files in output into a list
|
||||||
input to another run() command. (just like a | in linux) return_stderr: return both stdout and stderr as a
|
:param valid_exitcodes: list of valid exit codes for this command (checks exit code of both sides of a pipe)
|
||||||
tuple. (only returns stderr from this side of the pipe)
|
Use [] to accept all exit codes.
|
||||||
|
:param readonly: make this True if the command doesn't make any changes and is safe to execute in testmode
|
||||||
|
:param hide_errors: don't show stderr output as error, instead show it as debugging output (use to hide expected errors)
|
||||||
|
:param pipe: Instead of executing, return a pipe-handle to be used to
|
||||||
|
input to another run() command. (just like a | in linux)
|
||||||
|
:param return_stderr: return both stdout and stderr as a tuple. (normally only returns stdout)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not valid_exitcodes:
|
if valid_exitcodes is None:
|
||||||
valid_exitcodes = [0]
|
valid_exitcodes = [0]
|
||||||
|
|
||||||
encoded_cmd = []
|
encoded_cmd = []
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from zfs_autobackup.ThinnerRule import ThinnerRule
|
|||||||
class ZfsAutobackup:
|
class ZfsAutobackup:
|
||||||
"""main class"""
|
"""main class"""
|
||||||
|
|
||||||
VERSION = "3.0.1-beta4"
|
VERSION = "3.0.1-beta8"
|
||||||
HEADER = "zfs-autobackup v{} - Copyright 2020 E.H.Eefting (edwin@datux.nl)".format(VERSION)
|
HEADER = "zfs-autobackup v{} - Copyright 2020 E.H.Eefting (edwin@datux.nl)".format(VERSION)
|
||||||
|
|
||||||
def __init__(self, argv, print_arguments=True):
|
def __init__(self, argv, print_arguments=True):
|
||||||
@ -23,8 +23,7 @@ class ZfsAutobackup:
|
|||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=self.HEADER,
|
description=self.HEADER,
|
||||||
epilog='When a filesystem fails, zfs_backup will continue and report the number of failures at that end. '
|
epilog='Full manual at: https://github.com/psy0rz/zfs_autobackup')
|
||||||
'Also the exit code will indicate the number of failures. Full manual at: https://github.com/psy0rz/zfs_autobackup')
|
|
||||||
parser.add_argument('--ssh-config', default=None, help='Custom ssh client config')
|
parser.add_argument('--ssh-config', default=None, help='Custom ssh client config')
|
||||||
parser.add_argument('--ssh-source', default=None,
|
parser.add_argument('--ssh-source', default=None,
|
||||||
help='Source host to get backup from. (user@hostname) Default %(default)s.')
|
help='Source host to get backup from. (user@hostname) Default %(default)s.')
|
||||||
|
|||||||
@ -489,8 +489,8 @@ class ZfsDataset:
|
|||||||
if self.zfs_node.readonly:
|
if self.zfs_node.readonly:
|
||||||
self.force_exists = True
|
self.force_exists = True
|
||||||
|
|
||||||
# check if transfer was really ok (exit codes have been wrong before due to bugs in zfs-utils and can be
|
# check if transfer was really ok (exit codes have been wrong before due to bugs in zfs-utils and some
|
||||||
# ignored by some parameters)
|
# errors should be ignored, thats where the ignore_exitcodes is for.)
|
||||||
if not self.exists:
|
if not self.exists:
|
||||||
self.error("error during transfer")
|
self.error("error during transfer")
|
||||||
raise (Exception("Target doesn't exist after transfer, something went wrong."))
|
raise (Exception("Target doesn't exist after transfer, something went wrong."))
|
||||||
|
|||||||
Reference in New Issue
Block a user