Add an initial version of the drbd8 upgrade tool
authorIustin Pop <iustin@google.com>
Mon, 4 Feb 2008 09:50:26 +0000 (09:50 +0000)
committerIustin Pop <iustin@google.com>
Mon, 4 Feb 2008 09:50:26 +0000 (09:50 +0000)
Reviewed-by: imsnah

tools/README.drbd8-upgrade [new file with mode: 0644]
tools/drbd8-upgrade [new file with mode: 0644]

diff --git a/tools/README.drbd8-upgrade b/tools/README.drbd8-upgrade
new file mode 100644 (file)
index 0000000..0f4c547
--- /dev/null
@@ -0,0 +1,28 @@
+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.
diff --git a/tools/drbd8-upgrade b/tools/drbd8-upgrade
new file mode 100644 (file)
index 0000000..058545a
--- /dev/null
@@ -0,0 +1,184 @@
+#!/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()