backup: Add backup-cli tool and use it to initialize the backups

This commit is contained in:
Christian Decker
2020-04-04 14:25:33 +02:00
parent c049069cff
commit 4e19c32444
4 changed files with 86 additions and 34 deletions

39
backup/backup-cli Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
from backup import FileBackend
import os
import click
import json
@click.command()
@click.argument("lightning-dir", type=click.Path(exists=True))
@click.argument("backup-dir", type=click.Path(exists=True))
def init(lightning_dir, backup_dir):
destination = 'file://' + os.path.join(backup_dir, 'backup.dbak')
backend = FileBackend(destination)
backend.version, backend.prev_version = 0, 0
backend.offsets = [512, 0]
backend.version_count = 0
backend.write_metadata()
lock_file = os.path.join(lightning_dir, "backup.lock")
with open(lock_file, "w") as f:
f.write(json.dumps({
'backend_url': destination,
}))
print("Initialized backup backend {destination}, you can now start c-lightning".format(
destination=destination,
))
@click.group()
def cli():
pass
cli.add_command(init)
if __name__ == "__main__":
cli()

View File

@@ -1,14 +1,13 @@
#!/usr/bin/env python3
from pyln.client import Plugin
from pprint import pprint
from collections import namedtuple
from pyln.client import Plugin
from typing import Mapping, Type
from urllib.parse import urlparse
import struct
import os
from typing import Mapping, Type, Optional
import json
import logging
import os
import struct
import sys
from binascii import hexlify
plugin = Plugin()
@@ -27,6 +26,7 @@ root.addHandler(handler)
# applied.
Change = namedtuple('Change', ['version', 'transaction'])
class Backend(object):
def __init__(self, destination: str):
raise NotImplementedError
@@ -40,6 +40,7 @@ class Backend(object):
def initialize(self) -> bool:
raise NotImplementedError
class FileBackend(Backend):
def __init__(self, destination: str):
self.version = None
@@ -116,9 +117,15 @@ class FileBackend(Backend):
self.prev_version, self.offsets[1] = 0, 0
return True
def resolve_backend_class(backend_url):
backend_map: Mapping[str, Type[Backend]] = {
'file': FileBackend,
}
p = urlparse(backend_url)
backend_cl = backend_map.get(p.scheme, None)
return backend_cl
def abort(reason: str) -> None:
plugin.log(reason)
@@ -165,7 +172,7 @@ def on_db_write(writes, data_version, plugin, **kwargs):
change = Change(data_version, writes)
if not hasattr(plugin, 'backend'):
plugin.early_writes.append(change)
return True
return {"result": "continue"}
else:
return apply_write(plugin, change)
@@ -175,7 +182,8 @@ def apply_write(plugin, change):
assert(check_first_write(plugin, change.version))
plugin.initialized = True
return plugin.backend.add_entry(change)
if plugin.backend.add_change(change):
return {"result": "continue"}
@plugin.init()
@@ -185,6 +193,17 @@ def on_init(options: Mapping[str, str], plugin: Plugin, **kwargs):
plugin.db_path = configs['wallet']
destination = options['backup-destination']
# Ensure that we don't inadventently switch the destination
if os.path.exists("backup.lock"):
d = json.load(open("backup.lock", 'r'))
if destination is None or destination == 'null':
destination = d['backend_url']
elif destination != d['backend_url']:
abort(
"The destination specified as option does not match the one "
"specified in backup.lock. Please check your settings"
)
if not plugin.db_path.startswith('sqlite3'):
abort("The backup plugin only works with the sqlite3 database.")
@@ -192,8 +211,7 @@ def on_init(options: Mapping[str, str], plugin: Plugin, **kwargs):
abort("You must specify a backup destination, possibly on a secondary disk.")
# Let's initialize the backed. First we need to figure out which backend to use.
p = urlparse(destination)
backend_cl = backend_map.get(p.scheme, None)
backend_cl = resolve_backend_class(destination)
if backend_cl is None:
abort("Could not find a backend for scheme {p.scheme}".format(p=p))

Binary file not shown.

View File

@@ -3,20 +3,23 @@ from pyln.client import RpcError
from pyln.testing.fixtures import *
import os
import time
import shutil
import subprocess
plugin_dir = os.path.dirname(__file__)
plugin_path = os.path.join(plugin_dir, "backup.py")
cli_path = os.path.join(os.path.dirname(__file__), "backup-cli")
def test_start(node_factory, directory):
bdest = os.path.join(directory, 'backup.dbak')
opts = {
'plugin': plugin_path,
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
'backup-destination': 'file://' + bdest,
}
shutil.copyfile(
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
os.path.join(directory, "backup.dbak")
)
subprocess.check_call([cli_path, "init", directory, directory])
l1 = node_factory.get_node(options=opts)
l1.daemon.wait_for_log(r'backup.py')
@@ -37,14 +40,12 @@ def test_tx_abort(node_factory, directory):
inbetween the hook call and the DB transaction.
"""
bdest = os.path.join(directory, 'backup.dbak')
opts = {
'plugin': plugin_path,
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
'backup-destination': 'file://' + bdest,
}
shutil.copyfile(
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
os.path.join(directory, "backup.dbak")
)
subprocess.check_call([cli_path, "init", directory, directory])
l1 = node_factory.get_node(options=opts)
l1.stop()
@@ -71,10 +72,7 @@ def test_failing_restore(nf, directory):
'plugin': plugin_path,
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
}
shutil.copyfile(
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
os.path.join(directory, "backup.dbak")
)
subprocess.check_call([cli_path, "init", directory, directory])
l1 = node_factory.get_node(options=opts)
l1.stop()
@@ -97,10 +95,7 @@ def test_intermittent_backup(node_factory, directory):
'plugin': plugin_path,
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
}
shutil.copyfile(
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
os.path.join(directory, "backup.dbak")
)
subprocess.check_call([cli_path, "init", directory, directory])
l1 = node_factory.get_node(options=opts)
# Now start without the plugin. This should work fine.