summaryrefslogtreecommitdiff
path: root/morphlib/exts
diff options
context:
space:
mode:
authorJonathan Maw <jonathan.maw@codethink.co.uk>2013-05-24 15:44:57 +0000
committerJonathan Maw <jonathan.maw@codethink.co.uk>2013-05-28 17:14:27 +0100
commita9cae253dc1371b4decf50b708985e1ba1a8fe32 (patch)
treedb12e84916e8ad8b0fc9e0821f41e8c4d8d89d7b /morphlib/exts
parent5df056c4c06f2a74ed4d5ee965c5bcf237295c58 (diff)
downloadmorph-a9cae253dc1371b4decf50b708985e1ba1a8fe32.tar.gz
Add ssh-rsync write extension
This is used to perform upgrades on running baserock systems. It requires rsync on the target system
Diffstat (limited to 'morphlib/exts')
-rwxr-xr-xmorphlib/exts/ssh-rsync.write181
1 files changed, 181 insertions, 0 deletions
diff --git a/morphlib/exts/ssh-rsync.write b/morphlib/exts/ssh-rsync.write
new file mode 100755
index 00000000..6fe1153d
--- /dev/null
+++ b/morphlib/exts/ssh-rsync.write
@@ -0,0 +1,181 @@
+#!/usr/bin/python
+# Copyright (C) 2013 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+'''A Morph deployment write extension for upgrading systems over ssh.'''
+
+
+import cliapp
+import os
+import sys
+import time
+import tempfile
+
+import morphlib.writeexts
+
+class SshRsyncWriteExtension(morphlib.writeexts.WriteExtension):
+
+ '''Upgrade a running baserock system with ssh and rsync.
+
+ It assumes the system is baserock-based and has a btrfs partition.
+
+ The location command line argument is the 'user@hostname' string
+ that will be passed to ssh and rsync
+
+ '''
+
+ def process_args(self, args):
+ if len(args) != 2:
+ raise cliapp.AppException('Wrong number of command line args')
+
+ temp_root, location = args
+
+ self.check_valid_target(location)
+ self.upgrade_remote_system(location, temp_root)
+
+ def upgrade_remote_system(self, location, temp_root):
+ root_disk = self.find_root_disk(location)
+ version_label = os.environ.get('VERSION_LABEL')
+
+ try:
+ self.status(msg='Creating remote mount point')
+ remote_mnt = cliapp.ssh_runcmd(location, ['mktemp', '-d']).strip()
+
+ self.status(msg='Mounting root disk')
+ cliapp.ssh_runcmd(location, ['mount', root_disk, remote_mnt])
+
+ version_root = os.path.join(remote_mnt, 'systems', version_label)
+ run_dir = os.path.join(version_root, 'run')
+ orig_dir = os.path.join(version_root, 'orig')
+ try:
+ 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)
+
+ self.status(msg='Creating "run" subvolume')
+ cliapp.ssh_runcmd(location, ['btrfs', 'subvolume',
+ 'snapshot', orig_dir, run_dir])
+
+ self.install_remote_kernel(location, version_root, temp_root)
+ except Exception as e:
+ try:
+ cliapp.ssh_runcmd(location,
+ ['btrfs', 'subvolume', 'delete', run_dir])
+ cliapp.ssh_runcmd(location,
+ ['btrfs', 'subvolume', 'delete', orig_dir])
+ cliapp.ssh_runcmd(location, ['rm', '-rf', version_root])
+ except:
+ pass
+ raise e
+
+ if self.bootloader_is_wanted():
+ self.update_remote_extlinux(location, remote_mnt,
+ version_label)
+ except:
+ raise
+ else:
+ self.status(msg='Removing temporary mounts')
+ cliapp.ssh_runcmd(location, ['umount', root_disk])
+ cliapp.ssh_runcmd(location, ['rmdir', remote_mnt])
+
+ def update_remote_extlinux(self, location, remote_mnt, version_label):
+ '''Install/reconfigure extlinux on location'''
+
+ self.status(msg='Creating extlinux.conf')
+ config = os.path.join(remote_mnt, 'extlinux.conf')
+ temp_file = tempfile.mkstemp()[1]
+ with open(temp_file, 'w') as f:
+ f.write('default linux\n')
+ f.write('timeout 1\n')
+ f.write('label linux\n')
+ f.write('kernel /systems/' + version_label + '/kernel\n')
+ f.write('append root=/dev/sda '
+ 'rootflags=subvol=systems/' + version_label + '/run '
+ 'init=/sbin/init rw\n')
+
+ cliapp.ssh_runcmd(location, ['mv', config, config+'~'])
+
+ try:
+ cliapp.runcmd(['rsync', '-a', temp_file,
+ '%s:%s' % (location, config)])
+ except Exception as e:
+ try:
+ cliapp.ssh_runcmd(location, ['mv', config+'~', config])
+ except:
+ pass
+ raise e
+
+ def create_remote_orig(self, location, version_root, remote_mnt,
+ temp_root):
+ '''Create the subvolume version_root/orig on location'''
+
+ 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])
+
+ cliapp.runcmd(['rsync', '-a', '--checksum', '--numeric-ids',
+ '--delete', temp_root, '%s:%s' % (location, new_orig)])
+
+ def get_old_orig(self, location, remote_mnt):
+ '''Identify which subvolume to snapshot from'''
+
+ # rawdisk upgrades use 'factory'
+ return os.path.join(remote_mnt, 'systems', 'factory', 'orig')
+
+ 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]
+
+ def install_remote_kernel(self, location, version_root, temp_root):
+ '''Install the kernel in temp_root inside version_root on location'''
+
+ self.status(msg='Installing kernel')
+ image_names = ['vmlinuz', 'zImage', 'uImage']
+ kernel_dest = os.path.join(version_root, 'kernel')
+ for name in image_names:
+ try_path = os.path.join(temp_root, 'boot', name)
+ if os.path.exists(try_path):
+ cliapp.runcmd(['rsync', '-a', try_path,
+ '%s:%s' % (location, kernel_dest)])
+
+ def check_valid_target(self, location):
+ try:
+ cliapp.ssh_runcmd(location, ['true'])
+ except Exception as e:
+ raise cliapp.AppException('%s does not respond to ssh:\n%s'
+ % (location, e))
+
+ try:
+ cliapp.ssh_runcmd(location, ['test', '-d', '/baserock'])
+ except:
+ raise cliapp.AppException('%s is not a baserock system' % location)
+
+ try:
+ cliapp.ssh_runcmd(location, ['which', 'rsync'])
+ except:
+ raise cliapp.AppException('%s does not have rsync')
+
+SshRsyncWriteExtension().run()