--- /dev/null
+The DRBD0.7 to DRBD8 upgrade script is just for converting the actual
+metadata format and Ganeti configuration.
+
+Before running the script, the following steps need to be done:
+ - make sure all disks (remote_raid1) are fully synced and working
+ correctly
+ - shutdown all instances and make sure no operations on the cluster
+ will be done until the end of the upgrade
+ - remove the drbd 0.7 kernel module and userspace tools
+ - install the drbd8 kernel module and userspace tools
+ - run the upgrade script (no arguments)
+
+After the upgrade, it is recommended to run ‘gnt-instance
+activate-disks’ for all instances and make sure the disks have started
+correctly.
+
+Currently there are some race conditions in the activate-disks that are
+triggered by the metadata conversion process. We're investigating these,
+but there is no data loss or risk; activating drbd manually (as
+secondaries on both sides) is enough to give the peers the chance to
+reestablish connections and sync fully.
+
+The script also saves the metadata from each device on the master under
+the path it's run from as files named "metadump-$node-$lv_name". In case
+of script failure, it should be possible to restore the original state
+of the cluster by formatting and restoring the metadata on the drbd
+devices and using the save copy of the config file. For this, it is
+recommended to save the output of the script.
--- /dev/null
+#!/usr/bin/python
+#
+
+# Copyright (C) 2006, 2007 Google Inc.
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.
+
+
+"""DRBD8 upgrade tool
+
+"""
+
+import sys
+
+from ganeti import constants
+from ganeti import cli
+from ganeti import logger
+from ganeti import errors
+from ganeti import utils
+from ganeti import config
+from ganeti import ssh
+
+
+def Log(msg):
+ """Simple function that prints out its argument.
+
+ """
+ print msg
+ sys.stdout.flush()
+
+
+def LogIndented(level, msg):
+ """Log a message indented."""
+ for line in msg.splitlines():
+ Log(" " * 4 * level + line)
+
+
+def CheckInstanceConfig(instance):
+ """Check if the instance has a correct configuration.
+
+ This checks that MD devices have only one child (the single DRBD
+ pair).
+
+ """
+ if instance.disk_template != constants.DT_REMOTE_RAID1:
+ return
+ for disk in instance.disks:
+ if len(disk.children) != 1:
+ raise errors.ConfigurationError("Instance %s drive %s has >1 mirrors" %
+ (instance.name, disk.iv_name))
+
+def CheckNodeStatus(node):
+ """Check node status.
+
+ This checks that there are no active DRBD devices on the node
+
+ """
+ result = ssh.SSHCall(node.name, "root", "cat /proc/drbd")
+ if result.failed:
+ raise errors.OpExecError("Can't read /proc/drbd on node %s" % node.name)
+ for line in result.stdout.splitlines():
+ if "cs:" in line and "cs:Unconfigured" not in line:
+ raise errors.OpExecError("Active drbd? /proc/drbd entry: %s" % line)
+
+ result = ssh.SSHCall(node.name, "root", "drbdmeta")
+ if result.failed:
+ raise errors.OpExecError("Failed to run drbdmeta on node %s" % node.name)
+
+
+def ConvertMetaOnNode(node, meta_dev):
+ """Convert a meta device on a node."""
+
+ LogIndented(2, "Updating %s:%s" % (node, meta_dev))
+ lv_name = meta_dev.replace("/dev/", "").replace("/", "_")
+
+ result = ssh.SSHCall(node, "root",
+ "drbdmeta /dev/drbd0 v07 %s 0 dump-md" % meta_dev)
+
+ if result.failed:
+ raise errors.OpExecError("Can't dump meta device %s" % meta_dev)
+ try:
+ bk_fh = open("metadump-%s-%s" % (node, lv_name), "w")
+ bk_fh.write(result.stdout)
+ bk_fh.close()
+ except EnvironmentError:
+ Log("Can't write meta dump!")
+ raise
+ result = ssh.SSHCall(node, "root", "drbdmeta --force /dev/drbd0 v08 %s 0"
+ " create-md" % meta_dev)
+ if result.failed:
+ raise errors.OpExecError("upgrading metadata for %s failed: %s" %
+ (meta_dev, result.output))
+ LogIndented(2, "Updated metadata:")
+ LogIndented(3, "%s%s" % (result.stderr, result.stdout))
+
+
+def ConvertDiskStorage(disk):
+ """Convert disk storage for a drbd disk.
+
+ """
+ for node in disk.logical_id[:2]:
+ meta_disk = disk.children[1]
+ meta_dev = meta_disk.StaticDevPath()
+ if meta_dev is None:
+ raise errors.OpPrereqError("Can't upgrade disk %s: can't find meta in"
+ " config?" % meta_disk)
+ ConvertMetaOnNode(node, meta_dev)
+ disk.dev_type = constants.LD_DRBD8
+
+
+def ConvertInstance(instance):
+ """Conver the instance to drbd8.
+
+ """
+ if instance.disk_template != constants.DT_REMOTE_RAID1:
+ return False
+ Log("Working on %s" % instance.name)
+ new_disks = []
+ for disk in instance.disks:
+ LogIndented(1, "Disk %s" % disk.iv_name)
+ drbd_disk = disk.children[0]
+ ConvertDiskStorage(drbd_disk)
+ drbd_disk.iv_name = disk.iv_name
+ new_disks.append(drbd_disk)
+ instance.disk_template = constants.DT_DRBD8
+ instance.disks = new_disks
+ return True
+
+def LockedMain():
+ """Main under lock."""
+
+ cfg = config.ConfigWriter()
+ ilist = [cfg.GetInstanceInfo(name) for name in cfg.GetInstanceList()]
+ for instance in ilist:
+ CheckInstanceConfig(instance)
+ nlist = [cfg.GetNodeInfo(name) for name in cfg.GetNodeList()]
+ for node in nlist:
+ CheckNodeStatus(node)
+ bkname = utils.CreateBackup(constants.CLUSTER_CONF_FILE)
+ Log("Created initial backup as %s" % bkname)
+ #for node in nlist:
+ # UpgradeNode(node)
+ for instance in ilist:
+ ConvertInstance(instance)
+ cfg.Update(instance)
+ Log("Done")
+ return 0
+
+def main():
+ """Main function."""
+
+ logger.SetupLogging(debug=False, program="ganeti/drbd8-upgrade")
+ try:
+ utils.Lock('cmd', max_retries=15, debug=True)
+ except errors.LockError, err:
+ logger.ToStderr(str(err))
+ return 1
+ try:
+ try:
+ retval = LockedMain()
+ except errors.GenericError, err:
+ retval, msg = cli.FormatError(err)
+ Log(msg)
+ finally:
+ utils.Unlock('cmd')
+ utils.LockCleanup()
+ return retval
+
+
+if __name__ == "__main__":
+ main()