Skip to content

Commit 8afbda3

Browse files
committed
backup & logs
1 parent 3571c2e commit 8afbda3

File tree

7 files changed

+124
-22
lines changed

7 files changed

+124
-22
lines changed

images/utils/launcher/config/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,3 +1133,6 @@ def dump_node_attr(node: str, attr: str) -> None:
11331133
# dump_node_attr(node, "cex_api_key")
11341134
# dump_node_attr(node, "cex_api_secret")
11351135
dump_node_attr(node, "margin")
1136+
1137+
def expand_path(self, value) -> str:
1138+
pass

images/utils/launcher/node/__init__.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import functools
44
import logging
5-
import os
65
import sys
76
import threading
87
from abc import ABC, abstractmethod
@@ -14,7 +13,7 @@
1413
from docker.errors import NotFound
1514
from docker.types import IPAMConfig, IPAMPool
1615

17-
from launcher.utils import get_hostfs_file, ArgumentParser, yes_or_no
16+
from launcher.utils import ArgumentParser, yes_or_no
1817
from .DockerTemplate import DockerTemplate
1918
from .arby import Arby
2019
from .base import Node, ContainerNotFound, NoWaiting
@@ -52,19 +51,25 @@ def execute(self, *args) -> None:
5251
class LogsCommand(Command):
5352
def create_parser(self) -> ArgumentParser:
5453
parser = ArgumentParser(prog="logs", description="fetch the logs of a container")
55-
parser.add_argument("--tail", "-t", metavar='N', type=int,
56-
help="number of lines to show from the end of the logs", default=100)
57-
parser.add_argument("--since", "-s")
54+
parser.add_argument("--tail", metavar='N',
55+
help="number of lines to show from the end of the logs (default \"100\")",
56+
default="100")
57+
parser.add_argument("--since",
58+
help="show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
59+
parser.add_argument("--until",
60+
help="show logs before a timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
61+
parser.add_argument("--follow", "-f", action="store_true",
62+
help="follow log output")
63+
parser.add_argument("--timestamps", "-t", action="store_true",
64+
help="show timestamps")
5865
parser.add_argument("service")
5966
return parser
6067

6168
def execute(self, *args) -> None:
6269
args = self.parser.parse_args(args)
6370
name = args.service
64-
tail = args.tail
65-
since = args.since
6671
service = self.get_service(name)
67-
for line in service.logs(tail=tail, since=since):
72+
for line in service.logs(tail=args.tail, since=args.since, until=args.until, follow=args.follow, timestamps=args.timestamps):
6873
print(line)
6974

7075

@@ -354,12 +359,12 @@ def status(self):
354359
print(f"{border_style}┌─%s─┬─%s─┐{RESET}" % ("─" * col1_width, "─" * col2_width))
355360
print(
356361
f"{border_style}{RESET} {title_style}%s{RESET} {border_style}{RESET} {title_style}%s{RESET} {border_style}{RESET}" % (
357-
col1_fmt % col1_title, col2_fmt % col2_title))
362+
col1_fmt % col1_title, col2_fmt % col2_title))
358363
for name in names:
359364
print(f"{border_style}├─%s─┼─%s─┤{RESET}" % ("─" * col1_width, "─" * col2_width))
360365
print(
361366
f"{border_style}{RESET} {service_style}%s{RESET} {border_style}{RESET} {border_style}%s{RESET} {border_style}{RESET}" % (
362-
col1_fmt % name, col2_fmt % ""))
367+
col1_fmt % name, col2_fmt % ""))
363368
print(f"{border_style}└─%s─┴─%s─┘{RESET}" % ("─" * col1_width, "─" * col2_width))
364369

365370
lock = threading.Lock()
@@ -372,10 +377,10 @@ def update_line(name, text, fetching=False):
372377
x = col1_width + 2
373378
if fetching:
374379
print(f"\033[%dA\033[%dC{border_style}%s{RESET}\033[%dD\033[%dB" % (
375-
y, x + 3, col2_fmt % text[:col2_width], x + col2_width + 3, y), end="")
380+
y, x + 3, col2_fmt % text[:col2_width], x + col2_width + 3, y), end="")
376381
else:
377382
print("\033[%dA\033[%dC%s\033[%dD\033[%dB" % (
378-
y, x + 3, col2_fmt % text[:col2_width], x + col2_width + 3, y), end="")
383+
y, x + 3, col2_fmt % text[:col2_width], x + col2_width + 3, y), end="")
379384
sys.stdout.flush()
380385

381386
result = {name: None for name in names}

images/utils/launcher/node/base.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,29 @@ def _get_container_created_timestamp(self):
296296
t = datetime.datetime.strptime(parts[0], "%Y-%m-%dT%H:%M:%S")
297297
return t
298298

299-
def logs(self, tail="all"):
300-
if self._container is None:
301-
return None
302-
t = self._get_container_created_timestamp()
303-
result = self._container.logs(since=t, tail=tail)
304-
return itertools.chain(result.decode().splitlines())
299+
def logs(self, tail: str = None, since: str = None, until: str = None, follow: bool = False, timestamps: bool = False):
300+
assert since is None, "String since is not supported yet"
301+
assert until is None, "String until is not supported yet"
302+
try:
303+
tail = int(tail)
304+
except:
305+
pass
306+
307+
kwargs = {
308+
"tail": tail,
309+
"follow": follow,
310+
"timestamps": timestamps,
311+
}
312+
313+
if follow:
314+
kwargs["stream"] = True
315+
316+
result = self.container.logs(**kwargs)
317+
if isinstance(result, bytes):
318+
return itertools.chain(result.decode().splitlines())
319+
else:
320+
for line in result:
321+
yield line.decode().rstrip()
305322

306323
def _compare_image(self, container: Container) -> bool:
307324
attrs = container.attrs

images/utils/launcher/node/image.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,11 @@ def _get_update_status(self) -> str:
228228
- LOCAL_MISSING: The cloud image exists but no local image.
229229
- LOCAL_ONLY: The image only exists locally.
230230
- UNAVAILABLE: The image is not found locally or remotely.
231+
- USE_LOCAL
231232
"""
232233
if self.node.node_config["use_local_image"]:
233234
self.cloud_metadata = None
234-
return "LOCAL_NEWER"
235+
return "USE_LOCAL"
235236

236237
local = self.local_metadata
237238

@@ -271,6 +272,8 @@ def get_update_action(self) -> str:
271272
action = "PULL"
272273
elif status == "UP_TO_DATE":
273274
action = "NONE"
275+
elif status == "USE_LOCAL":
276+
action = "NONE"
274277
else:
275278
raise Exception("Unexpected status " + status)
276279

images/utils/launcher/node/lnd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def get_current_height(self):
127127
try:
128128
c = self.get_container()
129129
since = datetime.now() - timedelta(hours=1)
130+
# TODO use base logs
130131
lines = c.logs(since=since).decode().splitlines()
131132
p = re.compile(r".*New block: height=(\d+),.*")
132133
for line in reversed(lines):

images/utils/launcher/node/xud.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
from __future__ import annotations
2+
13
import functools
24
import json
35
import logging
46
import os
57
import re
68
from concurrent.futures import wait
7-
from threading import Event, Thread
8-
from typing import List
9+
from threading import Event
10+
from typing import List, Optional
911

1012
from launcher.table import ServiceTable
11-
from launcher.utils import yes_or_no, get_percentage
13+
from launcher.utils import yes_or_no, normalize_path
1214
from .base import Node, CliBackend, CliError
1315
from .lnd import CFHeaderState
1416

@@ -219,9 +221,79 @@ def _setup_wallets(self) -> None:
219221
self._restore_wallets()
220222
break
221223

224+
@property
225+
def backup_dir(self) -> Optional[str]:
226+
value_file = os.path.join(self.data_dir, ".backup-dir-value")
227+
if os.path.exists(value_file):
228+
with open(value_file) as f:
229+
value = f.read().strip()
230+
value = value.replace("/mnt/hostfs", "")
231+
return value
232+
return None
233+
234+
def update_backup_dir(self, value: str) -> None:
235+
cmd = "/update-backup-dir.sh '/mnt/hostfs{}'".format(value)
236+
exit_code, output = self.exec(cmd)
237+
print(output)
238+
if exit_code != 0:
239+
raise Exception("Failed to update backup location")
240+
241+
def _check_backup_dir(self, value: str) -> None:
242+
value = "/mnt/hostfs" + value
243+
244+
if not os.path.exists(value):
245+
raise Exception("not existed")
246+
247+
if not os.path.isdir(value):
248+
raise Exception("not a directory")
249+
250+
if not os.access(value, os.R_OK):
251+
raise Exception("not readable")
252+
253+
if not os.access(value, os.W_OK):
254+
raise Exception("not writable")
255+
222256
def _setup_backup(self) -> None:
223257
logger.info("Setup backup")
224258

259+
current_backup_dir = self.backup_dir
260+
261+
if current_backup_dir:
262+
backup_dir = self.config.backup_dir
263+
264+
if backup_dir:
265+
if current_backup_dir != backup_dir:
266+
self.update_backup_dir(backup_dir)
267+
else:
268+
backup_dir = self.config.backup_dir
269+
270+
if not backup_dir:
271+
print()
272+
print("Please enter a path to a destination where to store a backup of your environment. "
273+
"It includes everything, but NOT your wallet balance which is secured by your XUD SEED. "
274+
"The path should be an external drive, like a USB or network drive, which is permanently "
275+
"available on your device since backups are written constantly.")
276+
print()
277+
278+
while True:
279+
reply = input("Enter path to backup location: ")
280+
reply = reply.strip()
281+
path = normalize_path(reply)
282+
print("Checking backup location... ", end="", flush=True)
283+
try:
284+
self._check_backup_dir(path)
285+
print("OK.")
286+
except Exception as e:
287+
print("Failed (%s)." % e)
288+
continue
289+
290+
self.update_backup_dir(path)
291+
break
292+
293+
else:
294+
if current_backup_dir != backup_dir:
295+
self.update_backup_dir(backup_dir)
296+
225297
def has_wallets(self) -> bool:
226298
nodekey = os.path.join(self.data_dir, "nodekey.dat")
227299
return os.path.exists(nodekey)

setup.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ docker run --rm -it \
394394
--name "$(get_utils_name)" \
395395
-v /var/run/docker.sock:/var/run/docker.sock \
396396
-v "$NETWORK_DIR:/root/$NETWORK" \
397+
-v /:/mnt/hostfs \
397398
-e LOG_TIMESTAMP="$LOG_TIMESTAMP" \
398399
-e NETWORK="$NETWORK" \
399400
-e NETWORK_DIR="$NETWORK_DIR" \

0 commit comments

Comments
 (0)