summaryrefslogtreecommitdiff
path: root/extensions/ssh-rsync.write
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/ssh-rsync.write')
-rwxr-xr-xextensions/ssh-rsync.write172
1 files changed, 172 insertions, 0 deletions
diff --git a/extensions/ssh-rsync.write b/extensions/ssh-rsync.write
new file mode 100755
index 00000000..6d596500
--- /dev/null
+++ b/extensions/ssh-rsync.write
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+# Copyright (C) 2013-2015 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+'''A Morph deployment write extension for upgrading systems over ssh.'''
+
+
+import contextlib
+import cliapp
+import os
+import sys
+import time
+import tempfile
+
+import morphlib.writeexts
+
+
+def ssh_runcmd_ignore_failure(location, command, **kwargs):
+ try:
+ return cliapp.ssh_runcmd(location, command, **kwargs)
+ except cliapp.AppException:
+ pass
+
+
+class SshRsyncWriteExtension(morphlib.writeexts.WriteExtension):
+
+ '''See ssh-rsync.write.help for documentation'''
+
+
+ def find_root_disk(self, location):
+ '''Read /proc/mounts on location to find which device contains "/"'''
+
+ 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:
+ 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:
+ yield
+ finally:
+ 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
+
+ def get_old_orig(self, location, remote_mnt):
+ '''Identify which subvolume to snapshot from'''
+
+ # rawdisk upgrades use 'default'
+ return os.path.join(remote_mnt, 'systems', 'default', '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)])
+
+ @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
+
+ 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)
+
+ 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_root = os.path.join(remote_mnt, 'systems',
+ version_label, 'orig')
+ config_sync = os.path.join(system_root, 'usr', 'bin',
+ 'baserock-system-config-sync')
+ version_manager = os.path.join(system_root, 'usr', 'bin',
+ 'system-version-manager')
+ with self._deployed_version(location, version_label,
+ config_sync, version_manager):
+ self.status(msg='Setting %(v)s as the new default system',
+ v=version_label)
+ cliapp.ssh_runcmd(location, [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()