summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPedro Alvarez <pedro.alvarez@codethink.co.uk>2014-02-07 12:15:34 +0000
committerPedro Alvarez <pedro.alvarez@codethink.co.uk>2014-02-14 12:02:25 +0000
commitc9059ab4cda29b65111c67bb7251dd37b83bf8f6 (patch)
treee5b5d652c87d03f083690108a56514a271e4eb83
parent20e5304f40148d43c6ab57dea0abe400475e7e9f (diff)
downloadtbdiff-c9059ab4cda29b65111c67bb7251dd37b83bf8f6.tar.gz
Add script to modify the bootloader and paralell OS.
This is part of the upgrades work. With this tool you can now switch between versions of the OS, remove a version and list all the versions present in the system. It also activate a bootloader menu to choose a version to boot. The menu is important to make sure the user can boot the old OS if the new kernel doesn't work.
-rw-r--r--Makefile.am1
-rw-r--r--configure.ac1
-rw-r--r--snapshot-mgr/Makefile.am20
-rwxr-xr-xsnapshot-mgr/snapshot-mgr199
4 files changed, 221 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 746bd9c..76706a7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,6 +20,7 @@ SUBDIRS = \
tbdiff-create \
tbdiff-deploy \
tb-switch \
+ snapshot-mgr \
tb-update \
baserock-system-config-sync \
tests
diff --git a/configure.ac b/configure.ac
index 17ec2dd..f7b8dee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -120,6 +120,7 @@ tbdiff/tbdiff-1.pc
tbdiff-create/Makefile
tbdiff-deploy/Makefile
tb-switch/Makefile
+snapshot-mgr/Makefile
tb-update/Makefile
baserock-system-config-sync/Makefile
tests/Makefile
diff --git a/snapshot-mgr/Makefile.am b/snapshot-mgr/Makefile.am
new file mode 100644
index 0000000..9f28153
--- /dev/null
+++ b/snapshot-mgr/Makefile.am
@@ -0,0 +1,20 @@
+# vi:set ts=8 sw=8 noet ai nocindent:
+# -
+# Copyright (c) 2011-2012 Codethink Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License Version 2 as
+# published by the Free Software Foundation.
+#
+# 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.
+# vi:set ts=8 sw=8 noet ai nocindent:
+
+bin_SCRIPTS = \
+ snapshot-mgr
diff --git a/snapshot-mgr/snapshot-mgr b/snapshot-mgr/snapshot-mgr
new file mode 100755
index 0000000..341482c
--- /dev/null
+++ b/snapshot-mgr/snapshot-mgr
@@ -0,0 +1,199 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 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.
+
+
+import argparse
+import subprocess
+import tempfile
+import os
+import sys
+
+class SnapMgr(object):
+
+ def _check_system_exists(self, system_name):
+ systems_list = self._get_systems()
+
+ if system_name not in systems_list:
+ print "ERROR: the system " + system_name + " doesn't exist"
+ sys.exit(1)
+
+
+ # To get the systems the script lists the systems under the 'systems'
+ # folder which are directories and are not symlinks
+ def _get_systems(self):
+ systems = os.path.join(self.mount_dir, 'systems')
+ return [filename for filename in os.listdir(systems)
+ if os.path.isdir(os.path.join(systems,filename))
+ and not os.path.islink(os.path.join(systems,filename))]
+
+ # To check which system is the default one, it checks the 'ontimeout'
+ # value in the extlinux.conf file. If it's not present, then pick
+ # the first of the present systems.
+ def _get_default(self):
+ extlinux = os.path.join(self.mount_dir, 'extlinux.conf')
+ for line in open(extlinux,'r'):
+ line = line.rstrip('\n')
+ key, value= line.split(' ', 1)
+ if key == "ontimeout":
+ return value
+ if key == "label":
+ break
+
+ return self.current_system
+
+ def _rewrite(self, default, systems):
+
+ temp_config = os.path.join(self.mount_dir, 'extlinux.tmp')
+ config = os.path.join(self.mount_dir, 'extlinux.conf')
+ with open(temp_config, 'w') as f:
+ f.write('default menu.c32\n')
+ f.write('timeout 50\n')
+ f.write('prompt 0\n')
+ f.write('ontimeout ' + default +'\n')
+ for system in systems:
+ f.write('label ' + system +'\n')
+ f.write('kernel /systems/'+ system +'/kernel\n')
+ f.write('append root=/dev/sda '
+ 'rootflags=subvol=systems/'+ system +'/run '
+ 'init=/sbin/init rw\n')
+ os.rename(temp_config,config)
+
+ default_path = os.path.join(self.mount_dir, 'systems', 'default')
+ if os.path.islink(default_path):
+ subprocess.call(['ln', '-sfn', default, default_path])
+
+
+ def list (self):
+ systems = self._get_systems()
+ for system in systems:
+ print system
+
+ def get_default(self):
+ print self._get_default()
+
+ def get_running(self):
+ print self.current_system
+
+ def update (self):
+ self._rewrite(self._get_default(), self._get_systems())
+
+ def _get_mount_info(self):
+ mountpoint = subprocess.check_output(['findmnt', '/', '-l', '-n',
+ '-o', 'SOURCE'])
+ device, subvolume = mountpoint.split('[', 1)
+ subvolume = subvolume.split('/run',1)[0]
+ current_system = os.path.basename(subvolume)
+ return device, current_system
+
+ def remove (self, system_name):
+ self._check_system_exists(system_name)
+
+ default_system = self._get_default()
+
+ if system_name == default_system:
+ print "ERROR: you can't remove the default system"
+ sys.exit(1)
+ if system_name == self.current_system:
+ print "ERROR: you can't remove the running system"
+ sys.exit(1)
+
+ system_root = os.path.join(self.mount_dir, 'systems', system_name)
+
+ subprocess.call(['btrfs', 'subvolume', 'delete',
+ os.path.join(system_root, 'run')])
+ subprocess.call(['btrfs', 'subvolume', 'delete',
+ os.path.join(system_root, 'orig')])
+ subprocess.call(['rm', '-r', system_root])
+
+ self._rewrite(default_system, self._get_systems())
+
+ def set_default (self, system_name):
+ self._check_system_exists(system_name)
+ self._rewrite(system_name, self._get_systems())
+
+ def __init__(self):
+ self._closed = False
+ self.device, self.current_system = self._get_mount_info()
+ self.mount_dir = tempfile.mkdtemp()
+ subprocess.call(['mount', self.device, self.mount_dir])
+
+ def close(self):
+ self._closed = True
+ subprocess.call(['umount', self.mount_dir])
+
+ def __del__(self):
+ # No exception was raised, so no cleanup is required
+ if self._closed:
+ return
+ self.close()
+
+
+# create the top-level parser
+parser = argparse.ArgumentParser(prog='snapshot-mgr')
+subparsers = parser.add_subparsers(help='sub-command help')
+
+# create the parser for the "list" command
+parser_list = subparsers.add_parser('list',
+ help='list show you a list of systems')
+parser_list.set_defaults(action = 'list')
+
+# create the parser for the "update" command
+parser_update = subparsers.add_parser('update',
+ help='Update the extlinux.conf file with the present systems')
+parser_update.set_defaults(action = 'update')
+
+# create the parser for the "get-default" command
+parser_update = subparsers.add_parser('get-default',
+ help='Prints the default system')
+parser_update.set_defaults(action = 'get-default')
+
+# create the parser for the "get-running" command
+parser_update = subparsers.add_parser('get-running',
+ help='Prints the running system')
+parser_update.set_defaults(action = 'get-running')
+
+# create the parser for the "remove" command
+parser_remove = subparsers.add_parser('remove', help='remove a system')
+parser_remove.add_argument('system_name', help='name of the system to remove')
+parser_remove.set_defaults(action = 'remove')
+
+# create the parser for the "set-default" command
+parser_remove = subparsers.add_parser('set-default',
+ help='set a system as default')
+parser_remove.add_argument('system_name', help='name of the system to set')
+parser_remove.set_defaults(action = 'set-default')
+args = parser.parse_args()
+
+action = args.action
+
+snap_manager = SnapMgr()
+
+if action == "list":
+ snap_manager.list()
+elif action == "update":
+ snap_manager.update()
+elif action == "remove":
+ snap_manager.remove(args.system_name)
+elif action == "set-default":
+ snap_manager.set_default(args.system_name)
+elif action == "get-default":
+ snap_manager.get_default()
+elif action == "get-running":
+ snap_manager.get_running()
+
+
+snap_manager.close()