Two small improvements to burnin
[ganeti-local] / tools / drbd8-upgrade
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """DRBD8 upgrade tool
23
24 """
25
26 import sys
27
28 from ganeti import constants
29 from ganeti import cli
30 from ganeti import logger
31 from ganeti import errors
32 from ganeti import utils
33 from ganeti import config
34 from ganeti import ssh
35
36
37 def Log(msg):
38   """Simple function that prints out its argument.
39
40   """
41   print msg
42   sys.stdout.flush()
43
44
45 def LogIndented(level, msg):
46   """Log a message indented."""
47   for line in msg.splitlines():
48     Log(" " * 4 * level + line)
49
50
51 def CheckInstanceConfig(instance):
52   """Check if the instance has a correct configuration.
53
54   This checks that MD devices have only one child (the single DRBD
55   pair).
56
57   """
58   if instance.disk_template != constants.DT_REMOTE_RAID1:
59     return
60   for disk in instance.disks:
61     if len(disk.children) != 1:
62       raise errors.ConfigurationError("Instance %s drive %s has >1 mirrors" %
63                                       (instance.name, disk.iv_name))
64
65 def CheckNodeStatus(node):
66   """Check node status.
67
68   This checks that there are no active DRBD devices on the node
69
70   """
71   result = ssh.SSHCall(node.name, "root", "cat /proc/drbd")
72   if result.failed:
73     raise errors.OpExecError("Can't read /proc/drbd on node %s" % node.name)
74   for line in result.stdout.splitlines():
75     if "cs:" in line and "cs:Unconfigured" not in line:
76       raise errors.OpExecError("Active drbd? /proc/drbd entry: %s" % line)
77
78   result = ssh.SSHCall(node.name, "root", "drbdmeta")
79   if result.failed:
80     raise errors.OpExecError("Failed to run drbdmeta on node %s" % node.name)
81
82
83 def ConvertMetaOnNode(node, meta_dev):
84   """Convert a meta device on a node."""
85
86   LogIndented(2, "Updating %s:%s" % (node, meta_dev))
87   lv_name = meta_dev.replace("/dev/", "").replace("/", "_")
88
89   result = ssh.SSHCall(node, "root",
90                        "drbdmeta /dev/drbd0 v07 %s 0 dump-md" % meta_dev)
91
92   if result.failed:
93     raise errors.OpExecError("Can't dump meta device %s" % meta_dev)
94   try:
95     bk_fh = open("metadump-%s-%s" % (node, lv_name), "w")
96     bk_fh.write(result.stdout)
97     bk_fh.close()
98   except EnvironmentError:
99     Log("Can't write meta dump!")
100     raise
101   result = ssh.SSHCall(node, "root", "drbdmeta --force /dev/drbd0 v08 %s 0"
102                        " create-md" % meta_dev)
103   if result.failed:
104     raise errors.OpExecError("upgrading metadata for %s failed: %s" %
105                              (meta_dev, result.output))
106   LogIndented(2, "Updated metadata:")
107   LogIndented(3, "%s%s" % (result.stderr, result.stdout))
108
109
110 def ConvertDiskStorage(disk):
111   """Convert disk storage for a drbd disk.
112
113   """
114   for node in disk.logical_id[:2]:
115     meta_disk = disk.children[1]
116     meta_dev = meta_disk.StaticDevPath()
117     if meta_dev is None:
118       raise errors.OpPrereqError("Can't upgrade disk %s: can't find meta in"
119                                  " config?" % meta_disk)
120     ConvertMetaOnNode(node, meta_dev)
121   disk.dev_type = constants.LD_DRBD8
122
123
124 def ConvertInstance(instance):
125   """Conver the instance to drbd8.
126
127   """
128   if instance.disk_template != constants.DT_REMOTE_RAID1:
129     return False
130   Log("Working on %s" % instance.name)
131   new_disks = []
132   for disk in instance.disks:
133     LogIndented(1, "Disk %s" % disk.iv_name)
134     drbd_disk = disk.children[0]
135     ConvertDiskStorage(drbd_disk)
136     drbd_disk.iv_name = disk.iv_name
137     new_disks.append(drbd_disk)
138   instance.disk_template = constants.DT_DRBD8
139   instance.disks = new_disks
140   return True
141
142 def LockedMain():
143   """Main under lock."""
144
145   cfg = config.ConfigWriter()
146   ilist = [cfg.GetInstanceInfo(name) for name in cfg.GetInstanceList()]
147   for instance in ilist:
148     CheckInstanceConfig(instance)
149   nlist = [cfg.GetNodeInfo(name) for name in cfg.GetNodeList()]
150   for node in nlist:
151     CheckNodeStatus(node)
152   bkname = utils.CreateBackup(constants.CLUSTER_CONF_FILE)
153   Log("Created initial backup as %s" % bkname)
154   #for node in nlist:
155   #  UpgradeNode(node)
156   for instance in ilist:
157     ConvertInstance(instance)
158     cfg.Update(instance)
159   Log("Done")
160   return 0
161
162 def main():
163   """Main function."""
164
165   logger.SetupLogging(debug=False, program="ganeti/drbd8-upgrade")
166   try:
167     utils.Lock('cmd', max_retries=15, debug=True)
168   except errors.LockError, err:
169     logger.ToStderr(str(err))
170     return 1
171   try:
172     try:
173       retval = LockedMain()
174     except errors.GenericError, err:
175       retval, msg = cli.FormatError(err)
176       Log(msg)
177   finally:
178     utils.Unlock('cmd')
179     utils.LockCleanup()
180   return retval
181
182
183 if __name__ == "__main__":
184   main()