diff options
author | Richard Maw <richard.maw@codethink.co.uk> | 2014-10-09 15:09:03 +0000 |
---|---|---|
committer | Richard Maw <richard.maw@codethink.co.uk> | 2014-10-10 13:01:26 +0000 |
commit | 340accc9a14dca989e161cbcbf1f5d732d44ff6f (patch) | |
tree | 9841205cafb45476cfd40bc2810d0c8bde59c715 /morphlib/exts | |
parent | 120b16734079e5899b04f76a8c3c37c9a3ae8b3a (diff) | |
download | morph-340accc9a14dca989e161cbcbf1f5d732d44ff6f.tar.gz |
ssh-rsync: Don't delete version if it exists
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.
Diffstat (limited to 'morphlib/exts')
-rwxr-xr-x | morphlib/exts/ssh-rsync.write | 182 |
1 files changed, 105 insertions, 77 deletions
diff --git a/morphlib/exts/ssh-rsync.write b/morphlib/exts/ssh-rsync.write index 775619ec..2391d48c 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,134 @@ 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]) + raise - 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]) + raise + 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]) + raise - # 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() |