summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-10-20 11:43:55 +0200
committerAnton Khirnov <anton@khirnov.net>2020-10-20 11:49:14 +0200
commit2ec39a6058d8d778e27945ddb43c6448ed61aabe (patch)
tree86ff4965e83673b0c6a0e20fa5a224aeefba0a5e
parent1a6e05e609b1ef45bdde97fcaf3944fc2824fd40 (diff)
TargetSSH(LXC)LVM: share the save() method
-rw-r--r--lbup/targets.py116
1 files changed, 53 insertions, 63 deletions
diff --git a/lbup/targets.py b/lbup/targets.py
index 7f85526..59db6db 100644
--- a/lbup/targets.py
+++ b/lbup/targets.py
@@ -207,6 +207,11 @@ class TargetSSHLVM(TargetSSH):
"""
_snapshot_size = None
+ """
+ PID of the process whose mount namespace we use.
+ """
+ _mntns_pid = None
+
def __init__(self, name, dirs, excludes = None, logger = None,
remote = None, remote_bupdir = None, snapshot_size = '20G'):
self._snapshot_size = snapshot_size
@@ -216,6 +221,22 @@ class TargetSSHLVM(TargetSSH):
def __str__(self):
return "%s{LVM:%s}" % (super().__str__(), self._snapshot_size)
+ def _root_remote(self):
+ """
+ The the remote for root operations, such as manipulating LVM.
+
+ Overridden in subclasses.
+ """
+ return SSHRemote(self._remote.host, self._remote.port,
+ 'root', self._remote.proxy_remote)
+
+ def _resolve_mntns(self, ssh):
+ """
+ Get the PID of the process whose mount namespace we use.
+ Always 1 here, overridden in subclasses.
+ """
+ return 1
+
def _resolve_mntdev(self, mntinfo):
"""
Find out which LV to snapshot.
@@ -308,14 +329,16 @@ class TargetSSHLVM(TargetSSH):
self._logger.debug('Removed snapshot %s', snapshot_fullname)
@contextlib.contextmanager
- def _mount_snapshot(self, ssh, devnum, mount_path):
+ def _mount_snapshot(self, ssh, devnum, mount_path, fstype):
"""
Return a context manager that creates a read-only LVM snapshot
for the specified LV device number and mounts it at mount_path,
then unmounts and destroys it at exit.
"""
with self._snapshot_lv(ssh, devnum) as lv_path:
- self._paramiko_exec_cmd(ssh, 'mount -oro %s %s' % (lv_path, mount_path))
+ self._paramiko_exec_cmd(ssh,
+ 'mount -t{fstype} -oro {lv_path} {mount_path}'.format(
+ fstype = fstype, lv_path = lv_path, mount_path = mount_path))
try:
yield None
finally:
@@ -325,10 +348,7 @@ class TargetSSHLVM(TargetSSH):
def save(self, dry_run = False):
with contextlib.ExitStack() as stack:
conn_tgt = stack.enter_context(_ssh_client.SSHConnection(self._remote))
-
- remote_root = SSHRemote(self._remote.host, self._remote.port,
- 'root', self._remote.proxy_remote)
- conn_root = stack.enter_context(_ssh_client.SSHConnection(remote_root))
+ conn_root = stack.enter_context(_ssh_client.SSHConnection(self._root_remote()))
# resolve the path to BUP_DIR on the remote
bupdir = self._resolve_remote_bupdir(conn_tgt)
@@ -340,15 +360,24 @@ class TargetSSHLVM(TargetSSH):
snapshot_mount = '%s/%s' % (bupdir, 'lbup_mount')
self._paramiko_exec_cmd(conn_tgt, 'mkdir -p -m 700 ' + snapshot_mount)
+ self._mntns_pid = self._resolve_mntns(conn_root)
+
# read and parse the mountinfo table
mntinfo = MountInfo(
- self._paramiko_exec_cmd(conn_root, 'cat /proc/1/mountinfo', decode = False))
+ self._paramiko_exec_cmd(conn_root, 'cat /proc/%d/mountinfo' % self._mntns_pid,
+ decode = False))
+
+ devnum, mountpoint, fstype = self._resolve_mntdev(mntinfo)
- devnum, mountpoint, _ = self._resolve_mntdev(mntinfo)
- self._logger.debug('Backup targets are at device %s, mounted at %s',
- "%d:%d" % (devnum >> 8, devnum & 255), mountpoint)
+ # make sure we have a valid fstype
+ fstype = fstype.decode('ascii')
+ if not re.fullmatch(r'\w+', fstype, re.ASCII):
+ raise BackupException('Invalid LV FS type', fstype)
+
+ self._logger.debug('Backup targets are at device %s(%s), mounted at %s',
+ "%d:%d" % (devnum >> 8, devnum & 255), fstype, mountpoint)
- stack.enter_context(self._mount_snapshot(conn_root, devnum, snapshot_mount))
+ stack.enter_context(self._mount_snapshot(conn_root, devnum, snapshot_mount, fstype))
bup_exec = ['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host),
'-d', bupdir]
@@ -396,8 +425,18 @@ class TargetSSHLXCLVM(TargetSSHLVM):
return "%s{LXC:%s/%s@[%s]}{LVM:%s}" % (super().__str__(), self._lxc_containername,
self._lxc_username, str(self._parent_remote), self._snapshot_size)
+ def _resolve_mntns(self, ssh):
+ # get the PID of the container's init
+ cmd = 'su -s /bin/sh -c "lxc-info -H -p -n {container}" {user}'.format(
+ container = self._lxc_containername, user = self._lxc_username)
+ container_pid = self._paramiko_exec_cmd(ssh, cmd).rstrip('\n')
+ return int(container_pid)
+
+ def _root_remote(self):
+ return self._parent_remote
+
@contextlib.contextmanager
- def _nsmount_snapshot(self, ssh, devnum, mount_path, container_pid, fstype):
+ def _mount_snapshot(self, ssh, devnum, mount_path, fstype):
"""
Return a context manager that creates a read-only LVM snapshot
for the specified LV device number and mounts it at mount_path
@@ -413,60 +452,11 @@ class TargetSSHLXCLVM(TargetSSHLVM):
# container mount namespace
self._paramiko_exec_cmd(ssh,
'nsmount m {pid} {mount_path} {lv_path} {fstype}'.format(
- pid = container_pid, mount_path = mount_path,
+ pid = self._mntns_pid, mount_path = mount_path,
lv_path = lv_path, fstype = fstype))
try:
yield None
finally:
self._paramiko_exec_cmd(ssh,
'nsmount u {pid} {mount_path}'.format(
- pid = container_pid, mount_path = mount_path))
-
- def save(self, dry_run = False):
- with contextlib.ExitStack() as stack:
- parent = stack.enter_context(_ssh_client.SSHConnection(self._parent_remote))
- container = stack.enter_context(_ssh_client.SSHConnection(self._remote))
-
- # resolve the path to BUP_DIR on the container
- container_bupdir = self._resolve_remote_bupdir(container)
-
- # make sure the mount directory exists
- # due to how bup index works, the mount directory has to stay the
- # same for each backup
- # we use BUP_DIR/lbup_mount
- container_mountpoint = '%s/%s' % (container_bupdir, 'lbup_mount')
- self._paramiko_exec_cmd(container, 'mkdir -p -m 700 ' + 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)
-
- # read and parse the mountinfo table for the container init
- # which runs in the container's mount namespace
- mntinfo = MountInfo(
- self._paramiko_exec_cmd(parent, 'cat /proc/%s/mountinfo' % container_pid,
- decode = False))
-
- lv_devnum, lv_mountpoint, lv_fstype = self._resolve_mntdev(mntinfo)
- # make sure we have a valid fstype
- lv_fstype = lv_fstype.decode('ascii')
- if not re.fullmatch(r'\w+', lv_fstype, re.ASCII):
- raise BackupException('Invalid LV FS type', lv_fstype)
- self._logger.debug('Backup targets are at device %s(%s), mounted at %s',
- "%d:%d" % (lv_devnum >> 8, lv_devnum & 255), lv_fstype, lv_mountpoint)
-
- stack.enter_context(self._nsmount_snapshot(parent, lv_devnum, container_mountpoint,
- container_pid, lv_fstype))
-
- # execute the backup
- bup_exec = ['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host),
- '-d', container_bupdir]
- save_opts = ['--graft=%s=%s' % (container_mountpoint, lv_mountpoint)]
- reparent = (lv_mountpoint, AbsPath(container_mountpoint))
- return self._do_save(bup_exec, dry_run,
- reparent = reparent,
- save_opts = save_opts, index_opts = ['--no-check-device'])
+ pid = self._mntns_pid, mount_path = mount_path))