Add more explicit help for command line
[ganeti-local] / tools / cfgupgrade
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2007, 2008 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 """Tool to upgrade the configuration file.
23
24 This code handles only the types supported by simplejson. As an example, "set"
25 is a "list". Old Pickle based configurations files are converted to JSON during
26 the process.
27
28 """
29
30
31 import os
32 import os.path
33 import sys
34 import re
35 import optparse
36 import tempfile
37 import simplejson
38
39 from ganeti import utils
40 from ganeti import cli
41
42
43 options = None
44 args = None
45
46
47 class Error(Exception):
48   """Generic exception"""
49   pass
50
51
52 # {{{ Support for old Pickle files
53 class UpgradeDict(dict):
54   """Base class for internal config classes.
55
56   """
57   def __setstate__(self, state):
58     self.update(state)
59
60   def __getstate__(self):
61     return self.copy()
62
63
64 def FindGlobal(module, name):
65   """Wraps Ganeti config classes to internal ones.
66
67   This function may only return types supported by simplejson.
68
69   """
70   if module == "ganeti.objects":
71     return UpgradeDict
72   elif module == "__builtin__" and name == "set":
73     return list
74
75   return getattr(sys.modules[module], name)
76
77
78 def ReadPickleFile(f):
79   """Reads an old Pickle configuration.
80
81   """
82   import cPickle
83
84   loader = cPickle.Unpickler(f)
85   loader.find_global = FindGlobal
86   return loader.load()
87
88
89 def IsPickleFile(f):
90   """Checks whether a file is using the Pickle format.
91
92   """
93   magic = f.read(128)
94   try:
95     return not re.match('^\s*\{', magic)
96   finally:
97     f.seek(-len(magic), 1)
98 # }}}
99
100
101 def ReadJsonFile(f):
102   """Reads a JSON file.
103
104   """
105   return simplejson.load(f)
106
107
108 def ReadConfig(path):
109   """Reads configuration file.
110
111   """
112   f = open(path, 'r')
113   try:
114     if IsPickleFile(f):
115       return ReadPickleFile(f)
116     else:
117       return ReadJsonFile(f)
118   finally:
119     f.close()
120
121
122 def WriteConfig(path, data):
123   """Writes the configuration file.
124
125   """
126   if not options.dry_run:
127     utils.CreateBackup(path)
128
129   (fd, name) = tempfile.mkstemp(dir=os.path.dirname(path))
130   f = os.fdopen(fd, 'w')
131   try:
132     try:
133       simplejson.dump(data, f)
134       f.flush()
135       if options.dry_run:
136         os.unlink(name)
137       else:
138         os.rename(name, path)
139     except:
140       os.unlink(name)
141       raise
142   finally:
143     f.close()
144
145
146 def UpdateFromVersion2To3(cfg):
147   """Updates the configuration from version 2 to 3.
148
149   """
150   if cfg['cluster']['config_version'] != 2:
151     return
152
153   # Add port pool
154   if 'tcpudp_port_pool' not in cfg['cluster']:
155     cfg['cluster']['tcpudp_port_pool'] = []
156
157   # Add bridge settings
158   if 'default_bridge' not in cfg['cluster']:
159     cfg['cluster']['default_bridge'] = 'xen-br0'
160   for inst in cfg['instances'].values():
161     for nic in inst['nics']:
162       if 'bridge' not in nic:
163         nic['bridge'] = None
164
165   cfg['cluster']['config_version'] = 3
166
167
168 # Main program
169 if __name__ == "__main__":
170   program = os.path.basename(sys.argv[0])
171
172   # Option parsing
173   parser = optparse.OptionParser(usage="%prog [options] <config-file>")
174   parser.add_option('--dry-run', dest='dry_run',
175                     action="store_true",
176                     help="Try to do the conversion, but don't write"
177                          " output file")
178   parser.add_option(cli.FORCE_OPT)
179   parser.add_option('--verbose', dest='verbose',
180                     action="store_true",
181                     help="Verbose output")
182   (options, args) = parser.parse_args()
183
184   # Option checking
185   if args:
186     cfg_file = args[0]
187   else:
188     raise Error("Configuration file not specified")
189
190   if not options.force:
191     usertext = ("%s MUST run on the master node. Is this the master"
192                 " node?" % program)
193     if not cli.AskUser(usertext):
194       sys.exit(1)
195
196   config = ReadConfig(cfg_file)
197
198   if options.verbose:
199     import pprint
200     print "Before upgrade:"
201     pprint.pprint(config)
202     print
203
204   UpdateFromVersion2To3(config)
205
206   if options.verbose:
207     print "After upgrade:"
208     pprint.pprint(config)
209     print
210
211   WriteConfig(cfg_file, config)
212
213   print "The configuration file has been updated successfully. Please run"
214   print "  gnt-cluster copyfile %s" % cfg_file
215   print "now."
216
217 # vim: set foldmethod=marker :