#!/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()