summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-02-14 11:37:28 +0100
committerAnton Khirnov <anton@khirnov.net>2020-02-14 11:37:28 +0100
commitb3e182b781935757c0f0aa22e820fa7c0a85ef5d (patch)
treeff5b793d413e4b68774fbec928a1e5cfae636690
parent946fdc6a4078e6dcaf8c2b87b5466583e2c18882 (diff)
lxvlvm: actually read from the snapshot
Also, check all the remote outputs more thoroughly.
-rw-r--r--bupper/targets.py110
1 files changed, 74 insertions, 36 deletions
diff --git a/bupper/targets.py b/bupper/targets.py
index 65fc22e..0df7105 100644
--- a/bupper/targets.py
+++ b/bupper/targets.py
@@ -3,7 +3,6 @@ from abc import ABC, abstractmethod
import contextlib
import errno
import logging
-import os.path
import re
import socket
import subprocess
@@ -43,6 +42,8 @@ class Target(ABC):
_index_opts = None
_save_opts = None
+ _path_prefix = ''
+
def __init__(self, name, dirs, excludes = None, logger = None):
if excludes is None:
excludes = []
@@ -60,13 +61,18 @@ class Target(ABC):
self._save_opts = []
def _do_save(self, bup_exec):
+ excludes = [self._path_prefix + '/' + e for e in self.excludes]
+ dirs = [self._path_prefix + '/' + d for d in self.dirs]
+
+ # index
cmd = bup_exec + ['index', '--update', '--one-file-system'] + self._index_opts
- cmd.extend(['--exclude=%s' % e for e in self.excludes])
- cmd.extend(self.dirs)
+ cmd.extend(['--exclude=%s' % e for e in excludes])
+ cmd.extend(dirs)
self._logger.debug('Executing index command: ' + str(cmd))
res_idx = subprocess.run(cmd, capture_output = True)
- cmd = bup_exec + ['save', '-n', self.name] + self._save_opts + self.dirs
+ # save
+ cmd = bup_exec + ['save', '-n', self.name] + self._save_opts + dirs
self._logger.debug('Executing save command: ' + str(cmd))
res_save = subprocess.run(cmd, capture_output = True)
@@ -94,7 +100,7 @@ class TargetLocal(Target):
class TargetSSH(Target):
_remote = None
- def __init__(self, name, dirs, excludes = None,
+ def __init__(self, name, dirs, excludes = None, logger = None,
remote = None):
if remote is None:
remote = _parse_name(name)
@@ -104,7 +110,7 @@ class TargetSSH(Target):
raise NotImplementedError('Specifying port not implemented')
self._remote = remote
- super().__init__(name, dirs, excludes)
+ super().__init__(name, dirs, excludes, logger)
def save(self, data_dir):
return self._do_save(['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host)])
@@ -120,19 +126,10 @@ class TargetSSHLXCLVM(TargetSSH):
_lxc_username = None
_lxc_containername = None
- _container_mountpoint = '/mnt/bupper'
-
- def __init__(self, name, dirs, excludes = None,
+ def __init__(self, name, dirs, excludes = None, logger = None,
target_remote = None, parent_remote = None,
lxc_username = None, lxc_containername = None,
snapshot_size = '20G'):
- dirs_snapshot = [os.path.join(self._container_mountpoint, d) for d in dirs]
- excludes_snapshot = None
- if excludes is not None:
- excludes_snapshot = [os.path.join(self._container_mountpoint, e) for e in excludes]
-
- super().__init__(name, dirs_snapshot, excludes_snapshot, target_remote)
-
if parent_remote is None:
raise ValueError('parent_remote not specified')
if lxc_username is None:
@@ -143,6 +140,8 @@ class TargetSSHLXCLVM(TargetSSH):
self._lxc_containername = lxc_containername
self._snapshot_size = snapshot_size
+ super().__init__(name, dirs, excludes, logger, target_remote)
+
def _paramiko_exec_cmd(self, client, cmd):
self._logger.debug('Client %s: executing command: %s' % (client, cmd))
@@ -160,7 +159,17 @@ class TargetSSHLXCLVM(TargetSSH):
if chan.exit_status != 0:
raise RemoteExecException('Error executing "%s"' % cmd,
chan.exit_status, err + out)
- return out.decode('utf-8', errors = 'backslashreplace')
+
+ out = out.decode('utf-8', errors = 'backslashreplace')
+ err = err.decode('utf-8', errors = 'backslashreplace')
+
+ self._logger.debug('Command successful.')
+ if len(out):
+ self._logger.debug('Command stdout: %s' % out)
+ if len(err):
+ self._logger.debug('Command stderr: %s' % err)
+
+ return out
def save(self, data_dir):
@@ -169,44 +178,73 @@ class TargetSSHLXCLVM(TargetSSH):
container = stack.enter_context(_ssh_client.SSHConnection(self._remote))
# create the mount directory
- self._container_mountpoint = self._paramiko_exec_cmd(container,
+ container_mountpoint = self._paramiko_exec_cmd(container,
'mktemp -d --tmpdir bupper.XXXXXXXX').rstrip('\n')
- if len(self._container_mountpoint) <= 1 or self._container_mountpoint[0] != '/':
- raise BackupException('Unexpected mount directory: %s' % self._container_mountpoint)
+ # make sure it's
+ # - non-empty
+ # - an absolute path
+ # - contains no whitespace
+ if (len(container_mountpoint) <= 1 or container_mountpoint[0] != '/' or
+ re.search(r'\s', container_mountpoint)):
+ raise BackupException('Unexpected mount directory: %s' % container_mountpoint)
stack.callback(lambda: self._paramiko_exec_cmd(container,
- 'rmdir %s' % self._container_mountpoint))
+ 'rmdir %s' % container_mountpoint))
- self._save_opts.extend(['--strip-path', self._container_mountpoint])
+ self._path_prefix = container_mountpoint
+ self._save_opts.extend(['--strip-path', container_mountpoint])
# get the PID of the container's init
cmd_template = 'su -s /bin/sh -c "{command}" %s' % self._lxc_username
container_pid = self._paramiko_exec_cmd(parent, cmd_template.format(
command = 'lxc-info -H -p -n %s' % self._lxc_containername)).rstrip('\n')
+ # make sure it's a number
if not re.fullmatch('[0-9]+', container_pid):
raise BackupException('Invalid container PID: %s' % container_pid)
# get the LV/VG for the container's rootfs
container_rootfs = self._paramiko_exec_cmd(parent, cmd_template.format(
- command = 'lxc-info -H -c lxc.rootfs.path -n %s' %
- self._lxc_containername))\
- .rstrip('\n')\
- .translate({ord(' ') : r'\040', ord('\t') : r'\011',
- ord('\n') : r'\012', ord('\\') : r'\0134'})
- if len(container_rootfs) <= 1 or container_rootfs[0] != '/':
+ command = 'lxc-info -H -c lxc.rootfs.path -n %s' %
+ self._lxc_containername)).rstrip('\n')
+ # oct-escape certain characters as they are in /proc/mounts
+ # see seq_path[_root]() in linux
+ container_rootfs = container_rootfs.translate(
+ { ord(' ') : r'\040', ord('\t') : r'\011',
+ ord('\n') : r'\012', ord('\\') : r'\0134'})
+ # make sure the rootfs path is
+ # - non-empty
+ # - an absolute path
+ # - contains no whitespace
+ if (len(container_rootfs) <= 1 or container_rootfs[0] != '/' or
+ re.search(r'\s', container_rootfs)):
raise BackupException('Unxpected container rootfs directory: %s' % container_rootfs)
- mountline = self._paramiko_exec_cmd(parent, 'grep "%s" /proc/mounts' %
- container_rootfs).rstrip('\n').split()
+ # find the device node and the filesystem type for the container rootfs
+ mountlines = self._paramiko_exec_cmd(parent,
+ 'grep "%s" /proc/mounts' % container_rootfs).splitlines()
+ if len(mountlines) != 1:
+ raise BackupException('Expected exactly one matching mount line for the '
+ 'container root, got %d' % len(mountlines))
+
+ mountline = mountlines[0].split()
if len(mountline) < 2 or mountline[1] != container_rootfs:
raise BackupException('Invalid mount line: %s' % mountline)
lv_path = mountline[0]
lv_fstype = mountline[2]
- if len(lv_path) <= 1 or lv_path[0] != '/' or len(lv_fstype) < 1:
+ # make sure the LV path is
+ # - non-empty
+ # - an absolute path
+ # - contains no whitespace
+ # and that the FS type is non-empty
+ if (len(lv_path) <= 1 or lv_path[0] != '/' or
+ re.search(r'\s', lv_path) or len(lv_fstype) < 1):
raise BackupException('Unexpected LV path/FS type: %s\t%s' % (lv_path, lv_fstype))
- lv_name, vg_name = self._paramiko_exec_cmd(parent,
- 'lvdisplay -C --noheadings -o lv_name,vg_name ' + lv_path)\
- .strip().split()
+ # find the LV and VG names
+ lvdisplay = self._paramiko_exec_cmd(parent,
+ 'lvdisplay -C --noheadings -o lv_name,vg_name ' + lv_path).split()
+ if len(lvdisplay) != 2:
+ raise BackupException('Unexpected lvdisplay output: %s' % str(lvdisplay))
+ lv_name, vg_name = lvdisplay
if len(lv_name) < 1 or len(vg_name) < 1:
raise BackupException('Unexpected LV/VG name: %s\t%s' % (lv_name, vg_name))
@@ -232,7 +270,7 @@ class TargetSSHLXCLVM(TargetSSH):
# container mount namespace
self._paramiko_exec_cmd(parent,
'nsmount m {pid} {mountpoint} {devpath} {fstype}'.format(
- pid = container_pid, mountpoint = self._container_mountpoint,
+ pid = container_pid, mountpoint = container_mountpoint,
devpath = '/dev/%s/%s' % (vg_name, snapshot_name),
fstype = lv_fstype))
@@ -241,6 +279,6 @@ class TargetSSHLXCLVM(TargetSSH):
finally:
self._paramiko_exec_cmd(parent,
'nsmount u {pid} {mountpoint}'.format(
- pid = container_pid, mountpoint = self._container_mountpoint))
+ pid = container_pid, mountpoint = container_mountpoint))
return ret