backup: fix flake8 code nits

This commit is contained in:
Michael Schmoock
2022-12-27 13:52:45 +01:00
committed by Christian Decker
parent 7953dc0011
commit 7f18073a3a
6 changed files with 53 additions and 25 deletions

View File

@@ -98,9 +98,9 @@ def on_init(options, **kwargs):
# IMPORTANT NOTE # IMPORTANT NOTE
# Putting RPC stuff in init() like the following can cause deadlocks! # Putting RPC stuff in init() like the following can cause deadlocks!
# See: https://github.com/lightningd/plugins/issues/209 # See: https://github.com/lightningd/plugins/issues/209
#configs = plugin.rpc.listconfigs() # configs = plugin.rpc.listconfigs()
#if not configs['wallet'].startswith('sqlite3'): # if not configs['wallet'].startswith('sqlite3'):
# kill("The backup plugin only works with the sqlite3 database.") # kill("The backup plugin only works with the sqlite3 database.")
def kill(message: str): def kill(message: str):

View File

@@ -1,11 +1,13 @@
import logging, os, struct import logging
import os
import struct
import shutil import shutil
import tempfile import tempfile
from typing import Iterator from typing import Iterator
from urllib.parse import urlparse from urllib.parse import urlparse
from backend import Backend, Change from backend import Backend, Change
class FileBackend(Backend): class FileBackend(Backend):
def __init__(self, destination: str, create: bool): def __init__(self, destination: str, create: bool):
self.version = None self.version = None

View File

@@ -8,6 +8,7 @@ import zlib
from backend import Change from backend import Change
class PacketType: class PacketType:
CHANGE = 0x01 CHANGE = 0x01
SNAPSHOT = 0x02 SNAPSHOT = 0x02
@@ -21,8 +22,10 @@ class PacketType:
COMPACT = 0x0a COMPACT = 0x0a
COMPACT_RES = 0x0b COMPACT_RES = 0x0b
PKT_CHANGE_TYPES = {PacketType.CHANGE, PacketType.SNAPSHOT} PKT_CHANGE_TYPES = {PacketType.CHANGE, PacketType.SNAPSHOT}
def recvall(sock: socket.socket, n: int) -> bytearray: def recvall(sock: socket.socket, n: int) -> bytearray:
'''Receive exactly n bytes from a socket.''' '''Receive exactly n bytes from a socket.'''
buf = bytearray(n) buf = bytearray(n)
@@ -35,28 +38,32 @@ def recvall(sock: socket.socket, n: int) -> bytearray:
ptr += count ptr += count
return buf return buf
def send_packet(sock: socket.socket, typ: int, payload: bytes) -> None: def send_packet(sock: socket.socket, typ: int, payload: bytes) -> None:
sock.sendall(struct.pack('!BI', typ, len(payload))) sock.sendall(struct.pack('!BI', typ, len(payload)))
sock.sendall(payload) sock.sendall(payload)
def recv_packet(sock: socket.socket) -> Tuple[int, bytes]: def recv_packet(sock: socket.socket) -> Tuple[int, bytes]:
(typ, length) = struct.unpack('!BI', recvall(sock, 5)) (typ, length) = struct.unpack('!BI', recvall(sock, 5))
payload = recvall(sock, length) payload = recvall(sock, length)
return (typ, payload) return (typ, payload)
def change_from_packet(typ, payload): def change_from_packet(typ, payload):
'''Convert a network packet to a Change object.''' '''Convert a network packet to a Change object.'''
if typ == PacketType.CHANGE: if typ == PacketType.CHANGE:
(version, ) = struct.unpack('!I', payload[0:4]) (version, ) = struct.unpack('!I', payload[0:4])
payload = zlib.decompress(payload[4:]) payload = zlib.decompress(payload[4:])
return Change(version=version, snapshot=None, return Change(version=version, snapshot=None,
transaction=[t.decode('UTF-8') for t in payload.split(b'\x00')]) transaction=[t.decode('UTF-8') for t in payload.split(b'\x00')])
elif typ == PacketType.SNAPSHOT: elif typ == PacketType.SNAPSHOT:
(version, ) = struct.unpack('!I', payload[0:4]) (version, ) = struct.unpack('!I', payload[0:4])
payload = zlib.decompress(payload[4:]) payload = zlib.decompress(payload[4:])
return Change(version=version, snapshot=payload, transaction=None) return Change(version=version, snapshot=payload, transaction=None)
raise ValueError('Not a change (typ {})'.format(typ)) raise ValueError('Not a change (typ {})'.format(typ))
def packet_from_change(entry): def packet_from_change(entry):
'''Convert a Change object to a network packet.''' '''Convert a Change object to a network packet.'''
if entry.snapshot is None: if entry.snapshot is None:

View File

@@ -1,10 +1,13 @@
import logging, socket, struct import logging
import socket
import struct
import json import json
import sys import sys
from typing import Tuple from typing import Tuple
from backend import Backend from backend import Backend
from protocol import PacketType, recvall, PKT_CHANGE_TYPES, change_from_packet, packet_from_change, send_packet, recv_packet from protocol import PacketType, PKT_CHANGE_TYPES, change_from_packet, packet_from_change, send_packet, recv_packet
class SystemdHandler(logging.Handler): class SystemdHandler(logging.Handler):
PREFIX = { PREFIX = {
@@ -31,6 +34,7 @@ class SystemdHandler(logging.Handler):
except Exception: except Exception:
self.handleError(record) self.handleError(record)
def setup_server_logging(mode, level): def setup_server_logging(mode, level):
root_logger = logging.getLogger() root_logger = logging.getLogger()
root_logger.setLevel(level.upper()) root_logger.setLevel(level.upper())
@@ -42,6 +46,7 @@ def setup_server_logging(mode, level):
else: else:
assert(mode == 'plain') assert(mode == 'plain')
class SocketServer: class SocketServer:
def __init__(self, addr: Tuple[str, int], backend: Backend) -> None: def __init__(self, addr: Tuple[str, int], backend: Backend) -> None:
self.backend = backend self.backend = backend
@@ -63,7 +68,7 @@ class SocketServer:
while True: while True:
try: try:
(typ, payload) = self._recv_packet() (typ, payload) = self._recv_packet()
except IOError as e: except IOError:
logging.info('Connection closed') logging.info('Connection closed')
break break
if typ in PKT_CHANGE_TYPES: if typ in PKT_CHANGE_TYPES:
@@ -86,8 +91,8 @@ class SocketServer:
elif typ == PacketType.REQ_METADATA: elif typ == PacketType.REQ_METADATA:
logging.debug('Received REQ_METADATA') logging.debug('Received REQ_METADATA')
blob = struct.pack("!IIIQ", 0x01, self.backend.version, blob = struct.pack("!IIIQ", 0x01, self.backend.version,
self.backend.prev_version, self.backend.prev_version,
self.backend.version_count) self.backend.version_count)
self._send_packet(PacketType.METADATA, blob) self._send_packet(PacketType.METADATA, blob)
elif typ == PacketType.RESTORE: elif typ == PacketType.RESTORE:
logging.info('Received RESTORE') logging.info('Received RESTORE')
@@ -118,7 +123,7 @@ class SocketServer:
conn, _ = self.bind.accept() conn, _ = self.bind.accept()
try: try:
self._handle_conn(conn) self._handle_conn(conn)
except Exception as e: except Exception:
logging.exception('Got exception') logging.exception('Got exception')
finally: finally:
conn.close() conn.close()

View File

@@ -1,37 +1,47 @@
from collections import namedtuple from collections import namedtuple
import json, logging, socket, re, struct, time import json
import logging
import socket
import re
import struct
import time
from typing import Tuple, Iterator from typing import Tuple, Iterator
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from backend import Backend, Change from backend import Backend, Change
from protocol import PacketType, recvall, PKT_CHANGE_TYPES, change_from_packet, packet_from_change, send_packet, recv_packet from protocol import PacketType, PKT_CHANGE_TYPES, change_from_packet, packet_from_change, send_packet, recv_packet
# Total number of reconnection tries # Total number of reconnection tries
RECONNECT_TRIES=5 RECONNECT_TRIES = 5
# Delay in seconds between reconnections (initial) # Delay in seconds between reconnections (initial)
RECONNECT_DELAY=5 RECONNECT_DELAY = 5
# Scale delay factor after each failure # Scale delay factor after each failure
RECONNECT_DELAY_BACKOFF=1.5 RECONNECT_DELAY_BACKOFF = 1.5
HostPortInfo = namedtuple('HostPortInfo', ['host', 'port', 'addrtype']) HostPortInfo = namedtuple('HostPortInfo', ['host', 'port', 'addrtype'])
SocketURLInfo = namedtuple('SocketURLInfo', ['target', 'proxytype', 'proxytarget']) SocketURLInfo = namedtuple('SocketURLInfo', ['target', 'proxytype', 'proxytarget'])
# Network address type. # Network address type.
class AddrType: class AddrType:
IPv4 = 0 IPv4 = 0
IPv6 = 1 IPv6 = 1
NAME = 2 NAME = 2
# Proxy type. Only SOCKS5 supported at the moment as this is sufficient for Tor. # Proxy type. Only SOCKS5 supported at the moment as this is sufficient for Tor.
class ProxyType: class ProxyType:
DIRECT = 0 DIRECT = 0
SOCKS5 = 1 SOCKS5 = 1
def parse_host_port(path: str) -> HostPortInfo: def parse_host_port(path: str) -> HostPortInfo:
'''Parse a host:port pair.''' '''Parse a host:port pair.'''
if path.startswith('['): # bracketed IPv6 address if path.startswith('['): # bracketed IPv6 address
eidx = path.find(']') eidx = path.find(']')
if eidx == -1: if eidx == -1:
raise ValueError('Unterminated bracketed host address.') raise ValueError('Unterminated bracketed host address.')
@@ -46,7 +56,7 @@ def parse_host_port(path: str) -> HostPortInfo:
if eidx == -1: if eidx == -1:
raise ValueError('Port number missing.') raise ValueError('Port number missing.')
host = path[0:eidx] host = path[0:eidx]
if re.match('\d+\.\d+\.\d+\.\d+$', host): # matches IPv4 address format if re.match(r'\d+\.\d+\.\d+\.\d+$', host): # matches IPv4 address format
addrtype = AddrType.IPv4 addrtype = AddrType.IPv4
else: else:
addrtype = AddrType.NAME addrtype = AddrType.NAME
@@ -59,6 +69,7 @@ def parse_host_port(path: str) -> HostPortInfo:
return HostPortInfo(host=host, port=port, addrtype=addrtype) return HostPortInfo(host=host, port=port, addrtype=addrtype)
def parse_socket_url(destination: str) -> SocketURLInfo: def parse_socket_url(destination: str) -> SocketURLInfo:
'''Parse a socket: URL to extract the information contained in it.''' '''Parse a socket: URL to extract the information contained in it.'''
url = urlparse(destination) url = urlparse(destination)
@@ -73,7 +84,7 @@ def parse_socket_url(destination: str) -> SocketURLInfo:
# reject unknown parameters (currently all of them) # reject unknown parameters (currently all of them)
qs = parse_qs(url.query) qs = parse_qs(url.query)
for (key, values) in qs.items(): for (key, values) in qs.items():
if key == 'proxy': # proxy=socks5:127.0.0.1:9050 if key == 'proxy': # proxy=socks5:127.0.0.1:9050
if len(values) != 1: if len(values) != 1:
raise ValueError('Proxy can only have one value') raise ValueError('Proxy can only have one value')
@@ -88,6 +99,7 @@ def parse_socket_url(destination: str) -> SocketURLInfo:
return SocketURLInfo(target=target, proxytype=proxytype, proxytarget=proxytarget) return SocketURLInfo(target=target, proxytype=proxytype, proxytarget=proxytarget)
class SocketBackend(Backend): class SocketBackend(Backend):
def __init__(self, destination: str, create: bool): def __init__(self, destination: str, create: bool):
self.version = None self.version = None
@@ -100,7 +112,7 @@ class SocketBackend(Backend):
if self.url.proxytype == ProxyType.DIRECT: if self.url.proxytype == ProxyType.DIRECT:
if self.url.target.addrtype == AddrType.IPv6: if self.url.target.addrtype == AddrType.IPv6:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else: # TODO NAME is assumed to be IPv4 for now else: # TODO NAME is assumed to be IPv4 for now
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
else: else:
assert(self.url.proxytype == ProxyType.SOCKS5) assert(self.url.proxytype == ProxyType.SOCKS5)
@@ -109,8 +121,8 @@ class SocketBackend(Backend):
self.sock.set_proxy(socks.SOCKS5, self.url.proxytarget.host, self.url.proxytarget.port) self.sock.set_proxy(socks.SOCKS5, self.url.proxytarget.host, self.url.proxytarget.port)
logging.info('Connecting to {}:{} (addrtype {}, proxytype {}, proxytarget {})...'.format( logging.info('Connecting to {}:{} (addrtype {}, proxytype {}, proxytarget {})...'.format(
self.url.target.host, self.url.target.port, self.url.target.addrtype, self.url.target.host, self.url.target.port, self.url.target.addrtype,
self.url.proxytype, self.url.proxytarget)) self.url.proxytype, self.url.proxytarget))
self.sock.connect((self.url.target.host, self.url.target.port)) self.sock.connect((self.url.target.host, self.url.target.port))
logging.info('Connected to {}'.format(self.destination)) logging.info('Connected to {}'.format(self.destination))
@@ -144,7 +156,7 @@ class SocketBackend(Backend):
retry = 0 retry = 0
retry_delay = RECONNECT_DELAY retry_delay = RECONNECT_DELAY
need_connect = False need_connect = False
while True: # Retry loop while True: # Retry loop
try: try:
if need_connect: if need_connect:
self.connect() self.connect()

View File

@@ -1,5 +1,4 @@
from backend import Backend from backend import Backend
from filebackend import FileBackend
import socketbackend import socketbackend
from flaky import flaky from flaky import flaky
from pyln.testing.fixtures import * # noqa: F401,F403 from pyln.testing.fixtures import * # noqa: F401,F403
@@ -7,6 +6,7 @@ from pyln.testing.utils import sync_blockheight
import os import os
import pytest import pytest
import subprocess import subprocess
import tempfile
plugin_dir = os.path.dirname(__file__) plugin_dir = os.path.dirname(__file__)
@@ -236,6 +236,7 @@ class DummyBackend(Backend):
def __init__(self): def __init__(self):
pass pass
def test_rewrite(): def test_rewrite():
tests = [ tests = [
( (
@@ -281,6 +282,7 @@ def test_compact(bitcoind, directory, node_factory):
tmp = tempfile.TemporaryDirectory() tmp = tempfile.TemporaryDirectory()
subprocess.check_call([cli_path, "restore", bdest, tmp.name]) subprocess.check_call([cli_path, "restore", bdest, tmp.name])
def test_parse_socket_url(): def test_parse_socket_url():
with pytest.raises(ValueError): with pytest.raises(ValueError):
# fail: invalid url scheme # fail: invalid url scheme