"""Tool to upgrade the configuration file.
-The upgrade is done by unpickling the configuration file into custom classes
-derivating from dict. We then update the configuration by modifying these
-dicts. To save the configuration, it's pickled into a buffer and unpickled
-again using the Ganeti objects before being finally pickled into a file.
-
-Not using the custom classes wouldn't allow us to rename or remove attributes
-between versions without loosing their values.
+This code handles only the types supported by simplejson. As an example, "set"
+is a "list". Old Pickle based configurations files are converted to JSON during
+the process.
"""
import os
import os.path
import sys
+import re
import optparse
-import cPickle
import tempfile
-from cStringIO import StringIO
+import simplejson
-from ganeti import objects
+from ganeti import utils
+from ganeti.cli import AskUser, FORCE_OPT
-class Error(Exception):
- """Generic exception"""
- pass
+options = None
+args = None
-def _BaseFindGlobal(module, name):
- """Helper function for the other FindGlobal functions.
- """
- return getattr(sys.modules[module], name)
+class Error(Exception):
+ """Generic exception"""
+ pass
-# Internal config representation
+# {{{ Support for old Pickle files
class UpgradeDict(dict):
"""Base class for internal config classes.
return self.copy()
-class UpgradeConfigData(UpgradeDict): pass
-class UpgradeCluster(UpgradeDict): pass
-class UpgradeNode(UpgradeDict): pass
-class UpgradeInstance(UpgradeDict): pass
-class UpgradeDisk(UpgradeDict): pass
-class UpgradeNIC(UpgradeDict): pass
-class UpgradeOS(UpgradeDict): pass
+def FindGlobal(module, name):
+ """Wraps Ganeti config classes to internal ones.
+ This function may only return types supported by simplejson.
-_ClassMap = {
- objects.ConfigData: UpgradeConfigData,
- objects.Cluster: UpgradeCluster,
- objects.Node: UpgradeNode,
- objects.Instance: UpgradeInstance,
- objects.Disk: UpgradeDisk,
- objects.NIC: UpgradeNIC,
- objects.OS: UpgradeOS,
-}
+ """
+ if module == "ganeti.objects":
+ return UpgradeDict
+ elif module == "__builtin__" and name == "set":
+ return list
-# Build mapping dicts
-WriteMapping = dict()
-ReadMapping = dict()
-for key, value in _ClassMap.iteritems():
- WriteMapping[value.__name__] = key
- ReadMapping[key.__name__] = value
+ return getattr(sys.modules[module], name)
-# Read config
-def _ReadFindGlobal(module, name):
- """Wraps Ganeti config classes to internal ones.
+def ReadPickleFile(f):
+ """Reads an old Pickle configuration.
"""
- if module == "ganeti.objects" and name in ReadMapping:
- return ReadMapping[name]
+ import cPickle
- return _BaseFindGlobal(module, name)
+ loader = cPickle.Unpickler(f)
+ loader.find_global = FindGlobal
+ return loader.load()
-def ReadConfig(path):
- """Reads configuration file.
+def IsPickleFile(f):
+ """Checks whether a file is using the Pickle format.
"""
- f = open(path, 'r')
+ magic = f.read(128)
try:
- loader = cPickle.Unpickler(f)
- loader.find_global = _ReadFindGlobal
- data = loader.load()
+ return not re.match('^\s*\{', magic)
finally:
- f.close()
+ f.seek(-len(magic), 1)
+# }}}
- return data
-
-# Write config
-def _WriteFindGlobal(module, name):
- """Maps our internal config classes to Ganeti's.
+def ReadJsonFile(f):
+ """Reads a JSON file.
"""
- if module == "__main__" and name in WriteMapping:
- return WriteMapping[name]
-
- return _BaseFindGlobal(module, name)
+ return simplejson.load(f)
-def WriteConfig(path, data, dry_run):
- """Writes the configuration file.
+def ReadConfig(path):
+ """Reads configuration file.
"""
- buf = StringIO()
+ f = open(path, 'r')
+ try:
+ if IsPickleFile(f):
+ return ReadPickleFile(f)
+ else:
+ return ReadJsonFile(f)
+ finally:
+ f.close()
- # Write intermediate representation
- dumper = cPickle.Pickler(buf, cPickle.HIGHEST_PROTOCOL)
- dumper.dump(data)
- del dumper
- # Convert back to Ganeti objects
- buf.seek(0)
- loader = cPickle.Unpickler(buf)
- loader.find_global = _WriteFindGlobal
- data = loader.load()
+def WriteConfig(path, data):
+ """Writes the configuration file.
+
+ """
+ if not options.dry_run:
+ utils.CreateBackup(path)
- # Write target file
(fd, name) = tempfile.mkstemp(dir=os.path.dirname(path))
f = os.fdopen(fd, 'w')
try:
try:
- dumper = cPickle.Pickler(f, cPickle.HIGHEST_PROTOCOL)
- dumper.dump(data)
+ simplejson.dump(data, f)
f.flush()
- if dry_run:
+ if options.dry_run:
os.unlink(name)
else:
os.rename(name, path)
# Add port pool
if 'tcpudp_port_pool' not in cfg['cluster']:
- cfg['cluster']['tcpudp_port_pool'] = set()
+ cfg['cluster']['tcpudp_port_pool'] = []
# Add bridge settings
if 'default_bridge' not in cfg['cluster']:
# Main program
if __name__ == "__main__":
+ program = os.path.basename(sys.argv[0])
+
# Option parsing
parser = optparse.OptionParser()
parser.add_option('--dry-run', dest='dry_run',
action="store_true",
- help="Try to do the conversion, but don't write "
- "output file")
+ help="Try to do the conversion, but don't write"
+ " output file")
+ parser.add_option(FORCE_OPT)
parser.add_option('--verbose', dest='verbose',
action="store_true",
help="Verbose output")
if args:
cfg_file = args[0]
else:
- raise Error, ("Configuration file not specified")
+ raise Error("Configuration file not specified")
+
+ if not options.force:
+ usertext = ("%s MUST run on the master node. Is this the master"
+ " node?" % program)
+ if not AskUser(usertext):
+ sys.exit(1)
config = ReadConfig(cfg_file)
+ if options.verbose:
+ import pprint
+ print "Before upgrade:"
+ pprint.pprint(config)
+ print
+
UpdateFromVersion2To3(config)
if options.verbose:
- import pprint
+ print "After upgrade:"
pprint.pprint(config)
+ print
+
+ WriteConfig(cfg_file, config)
+
+ print "The configuration file has been updated successfully. Please run"
+ print " gnt-cluster copyfile %s" % cfg_file
+ print "now."
- WriteConfig(cfg_file, config, options.dry_run)
+# vim: set foldmethod=marker :