summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2014-10-09 15:09:03 (GMT)
committerRichard Maw <richard.maw@codethink.co.uk>2014-10-09 15:09:03 (GMT)
commit36332e59ef5a1e86b9b3188c9b9e09754bd0b695 (patch)
tree95f06905ad906794e756cfddf19815c052b030ba
parent120b16734079e5899b04f76a8c3c37c9a3ae8b3a (diff)
downloadmorph-baserock/richardmaw/fix-ssh-rsync-destroying-versions.tar.gz
ssh-rsync: Don't delete version if it existsbaserock/richardmaw/fix-ssh-rsync-destroying-versions
A quirk in the resource cleanup code meant that if you gave the same version label when deploying a new version, then it would fail, then remove the old version, as it had assumed that it was the one to create those directories. This patch fixes this issue by making short context managers for all the resource allocation, so cleanup is done by walking up the context managers, so only the mount and the temporary directory need to be cleaned up if the `mkdir "$VERSION_ROOT"` fails. I've tested this with a deploy of a version that doesn't already exist, and the version I'm currently running, so I can conclusively say it's fixed that problem.
-rwxr-xr-xmorphlib/exts/ssh-rsync.write179
1 files changed, 102 insertions, 77 deletions
diff --git a/morphlib/exts/ssh-rsync.write b/morphlib/exts/ssh-rsync.write
index 775619e..d26db39 100755
--- a/morphlib/exts/ssh-rsync.write
+++ b/morphlib/exts/ssh-rsync.write
@@ -18,6 +18,7 @@
'''A Morph deployment write extension for upgrading systems over ssh.'''
+import contextlib
import cliapp
import os
import sys
@@ -45,107 +46,131 @@ class SshRsyncWriteExtension(morphlib.writeexts.WriteExtension):
'''
- def process_args(self, args):
- if len(args) != 2:
- raise cliapp.AppException('Wrong number of command line args')
-
- temp_root, location = args
-
- self.upgrade_remote_system(location, temp_root)
-
- def upgrade_remote_system(self, location, temp_root):
- root_disk = self.find_root_disk(location)
- uuid = cliapp.ssh_runcmd(location, ['blkid', '-s', 'UUID', '-o',
- 'value', root_disk]).strip()
-
- self.complete_fstab_for_btrfs_layout(temp_root, uuid)
+ def find_root_disk(self, location):
+ '''Read /proc/mounts on location to find which device contains "/"'''
- version_label = os.environ['VERSION_LABEL']
- autostart = self.get_environment_boolean('AUTOSTART')
+ self.status(msg='Finding device that contains "/"')
+ contents = cliapp.ssh_runcmd(location, ['cat', '/proc/mounts'])
+ for line in contents.splitlines():
+ line_words = line.split()
+ if (line_words[1] == '/' and line_words[0] != 'rootfs'):
+ return line_words[0]
+ @contextlib.contextmanager
+ def _remote_mount_point(self, location):
self.status(msg='Creating remote mount point')
remote_mnt = cliapp.ssh_runcmd(location, ['mktemp', '-d']).strip()
try:
- self.status(msg='Mounting root disk')
- cliapp.ssh_runcmd(location, ['mount', root_disk, remote_mnt])
- except Exception as e:
- ssh_runcmd_ignore_failure(location, ['rmdir', remote_mnt])
- raise e
+ yield remote_mnt
+ finally:
+ self.status(msg='Removing remote mount point')
+ cliapp.ssh_runcmd(location, ['rmdir', remote_mnt])
+ @contextlib.contextmanager
+ def _remote_mount(self, location, root_disk, mountpoint):
+ self.status(msg='Mounting root disk')
+ cliapp.ssh_runcmd(location, ['mount', root_disk, mountpoint])
try:
- version_root = os.path.join(remote_mnt, 'systems', version_label)
- orig_dir = os.path.join(version_root, 'orig')
-
- self.status(msg='Creating %s' % version_root)
- cliapp.ssh_runcmd(location, ['mkdir', version_root])
-
- self.create_remote_orig(location, version_root, remote_mnt,
- temp_root)
-
- # Use the system-version-manager from the new system we just
- # installed, so that we can upgrade from systems that don't have
- # it installed.
- self.status(msg='Calling system-version-manager to deploy upgrade')
- deployment = os.path.join('/systems', version_label, 'orig')
- system_config_sync = os.path.join(
- remote_mnt, 'systems', version_label, 'orig', 'usr', 'bin',
- 'baserock-system-config-sync')
- system_version_manager = os.path.join(
- remote_mnt, 'systems', version_label, 'orig', 'usr', 'bin',
- 'system-version-manager')
- cliapp.ssh_runcmd(location,
- ['env', 'BASEROCK_SYSTEM_CONFIG_SYNC='+system_config_sync,
- system_version_manager, 'deploy', deployment])
-
- self.status(msg='Setting %s as the new default system' %
- version_label)
- cliapp.ssh_runcmd(location,
- [system_version_manager, 'set-default', version_label])
- except Exception as e:
- self.status(msg='Deployment failed')
- ssh_runcmd_ignore_failure(
- location, ['btrfs', 'subvolume', 'delete', orig_dir])
- ssh_runcmd_ignore_failure(
- location, ['rm', '-rf', version_root])
- raise e
+ yield
finally:
- self.status(msg='Removing temporary mounts')
- cliapp.ssh_runcmd(location, ['umount', remote_mnt])
- cliapp.ssh_runcmd(location, ['rmdir', remote_mnt])
+ self.status(msg='Unmounting root disk')
+ cliapp.ssh_runcmd(location, ['umount', mountpoint])
+
+ @contextlib.contextmanager
+ def _created_version_root(self, location, remote_mnt, version_label):
+ version_root = os.path.join(remote_mnt, 'systems', version_label)
+ self.status(msg='Creating %(root)s', root=version_root)
+ cliapp.ssh_runcmd(location, ['mkdir', version_root])
+ try:
+ yield version_root
+ except BaseException as e:
+ # catch all, we always want to clean up
+ self.status(msg='Cleaning up %(root)s', root=version_root)
+ ssh_runcmd_ignore_failure(location, ['rmdir', version_root])
- if autostart:
- self.status(msg="Rebooting into new system ...")
- ssh_runcmd_ignore_failure(location, ['reboot'])
+ def get_old_orig(self, location, remote_mnt):
+ '''Identify which subvolume to snapshot from'''
- def create_remote_orig(self, location, version_root, remote_mnt,
- temp_root):
- '''Create the subvolume version_root/orig on location'''
+ # rawdisk upgrades use 'factory'
+ return os.path.join(remote_mnt, 'systems', 'factory', 'orig')
+ @contextlib.contextmanager
+ def _created_orig_subvolume(self, location, remote_mnt, version_root):
self.status(msg='Creating "orig" subvolume')
old_orig = self.get_old_orig(location, remote_mnt)
new_orig = os.path.join(version_root, 'orig')
cliapp.ssh_runcmd(location, ['btrfs', 'subvolume', 'snapshot',
old_orig, new_orig])
+ try:
+ yield new_orig
+ except BaseException as e:
+ ssh_runcmd_ignore_failure(
+ location, ['btrfs', 'subvolume', 'delete', new_orig])
+ def populate_remote_orig(self, location, new_orig, temp_root):
+ '''Populate the subvolume version_root/orig on location'''
+
+ self.status(msg='Populating "orig" subvolume')
cliapp.runcmd(['rsync', '-as', '--checksum', '--numeric-ids',
'--delete', temp_root + os.path.sep,
'%s:%s' % (location, new_orig)])
- def get_old_orig(self, location, remote_mnt):
- '''Identify which subvolume to snapshot from'''
+ @contextlib.contextmanager
+ def _deployed_version(self, location, version_label,
+ system_config_sync, system_version_manager):
+ self.status(msg='Calling system-version-manager to deploy upgrade')
+ deployment = os.path.join('/systems', version_label, 'orig')
+ cliapp.ssh_runcmd(location,
+ ['env', 'BASEROCK_SYSTEM_CONFIG_SYNC='+system_config_sync,
+ system_version_manager, 'deploy', deployment])
+ try:
+ yield deployment
+ except BaseException as e:
+ self.status(msg='Cleaning up failed version installation')
+ cliapp.ssh_runcmd(location,
+ [system_version_manager, 'remove', version_label])
- # rawdisk upgrades use 'factory'
- return os.path.join(remote_mnt, 'systems', 'factory', 'orig')
+ def upgrade_remote_system(self, location, temp_root):
+ root_disk = self.find_root_disk(location)
+ uuid = cliapp.ssh_runcmd(location, ['blkid', '-s', 'UUID', '-o',
+ 'value', root_disk]).strip()
- def find_root_disk(self, location):
- '''Read /proc/mounts on location to find which device contains "/"'''
+ self.complete_fstab_for_btrfs_layout(temp_root, uuid)
- self.status(msg='Finding device that contains "/"')
- contents = cliapp.ssh_runcmd(location, ['cat', '/proc/mounts'])
- for line in contents.splitlines():
- line_words = line.split()
- if (line_words[1] == '/' and line_words[0] != 'rootfs'):
- return line_words[0]
+ version_label = os.environ['VERSION_LABEL']
+ autostart = self.get_environment_boolean('AUTOSTART')
+
+ with self._remote_mount_point(location) as remote_mnt, \
+ self._remote_mount(location, root_disk, remote_mnt), \
+ self._created_version_root(location, remote_mnt,
+ version_label) as version_root, \
+ self._created_orig_subvolume(location, remote_mnt,
+ version_root) as orig:
+ self.populate_remote_orig(location, orig, temp_root)
+ system_config_sync = os.path.join(
+ remote_mnt, 'systems', version_label, 'orig',
+ 'usr', 'bin', 'baserock-system-config-sync')
+ system_version_manager = os.path.join(
+ remote_mnt, 'systems', version_label, 'orig',
+ 'usr', 'bin', 'system-version-manager')
+ with self._deployed_version(location, version_label,
+ system_config_sync, system_version_manager):
+ self.status(msg='Setting %(v)s as the new default system',
+ v=version_label)
+ cliapp.ssh_runcmd(location, [system_version_manager,
+ 'set-default', version_label])
+
+ if autostart:
+ self.status(msg="Rebooting into new system ...")
+ ssh_runcmd_ignore_failure(location, ['reboot'])
+
+ def process_args(self, args):
+ if len(args) != 2:
+ raise cliapp.AppException('Wrong number of command line args')
+
+ temp_root, location = args
+
+ self.upgrade_remote_system(location, temp_root)
SshRsyncWriteExtension().run()