Revision 319856a9 tools/cfgupgrade

b/tools/cfgupgrade
21 21

  
22 22
"""Tool to upgrade the configuration file.
23 23

  
24
The upgrade is done by unpickling the configuration file into custom classes
25
derivating from dict. We then update the configuration by modifying these
26
dicts. To save the configuration, it's pickled into a buffer and unpickled
27
again using the Ganeti objects before being finally pickled into a file.
28

  
29
Not using the custom classes wouldn't allow us to rename or remove attributes
30
between versions without loosing their values.
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.
31 27

  
32 28
"""
33 29

  
......
35 31
import os
36 32
import os.path
37 33
import sys
34
import re
38 35
import optparse
39
import cPickle
40 36
import tempfile
41
from cStringIO import StringIO
37
import simplejson
42 38

  
43
from ganeti import objects
39
from ganeti import utils
40
from ganeti.cli import AskUser, FORCE_OPT
44 41

  
45
class Error(Exception):
46
  """Generic exception"""
47
  pass
48 42

  
43
options = None
44
args = None
49 45

  
50
def _BaseFindGlobal(module, name):
51
  """Helper function for the other FindGlobal functions.
52 46

  
53
  """
54
  return getattr(sys.modules[module], name)
47
class Error(Exception):
48
  """Generic exception"""
49
  pass
55 50

  
56 51

  
57
# Internal config representation
52
# {{{ Support for old Pickle files
58 53
class UpgradeDict(dict):
59 54
  """Base class for internal config classes.
60 55

  
......
66 61
    return self.copy()
67 62

  
68 63

  
69
class UpgradeConfigData(UpgradeDict): pass
70
class UpgradeCluster(UpgradeDict): pass
71
class UpgradeNode(UpgradeDict): pass
72
class UpgradeInstance(UpgradeDict): pass
73
class UpgradeDisk(UpgradeDict): pass
74
class UpgradeNIC(UpgradeDict): pass
75
class UpgradeOS(UpgradeDict): pass
64
def FindGlobal(module, name):
65
  """Wraps Ganeti config classes to internal ones.
76 66

  
67
  This function may only return types supported by simplejson.
77 68

  
78
_ClassMap = {
79
  objects.ConfigData: UpgradeConfigData,
80
  objects.Cluster: UpgradeCluster,
81
  objects.Node: UpgradeNode,
82
  objects.Instance: UpgradeInstance,
83
  objects.Disk: UpgradeDisk,
84
  objects.NIC: UpgradeNIC,
85
  objects.OS: UpgradeOS,
86
}
69
  """
70
  if module == "ganeti.objects":
71
    return UpgradeDict
72
  elif module == "__builtin__" and name == "set":
73
    return list
87 74

  
88
# Build mapping dicts
89
WriteMapping = dict()
90
ReadMapping = dict()
91
for key, value in _ClassMap.iteritems():
92
  WriteMapping[value.__name__] = key
93
  ReadMapping[key.__name__] = value
75
  return getattr(sys.modules[module], name)
94 76

  
95 77

  
96
# Read config
97
def _ReadFindGlobal(module, name):
98
  """Wraps Ganeti config classes to internal ones.
78
def ReadPickleFile(f):
79
  """Reads an old Pickle configuration.
99 80

  
100 81
  """
101
  if module == "ganeti.objects" and name in ReadMapping:
102
    return ReadMapping[name]
82
  import cPickle
103 83

  
104
  return _BaseFindGlobal(module, name)
84
  loader = cPickle.Unpickler(f)
85
  loader.find_global = FindGlobal
86
  return loader.load()
105 87

  
106 88

  
107
def ReadConfig(path):
108
  """Reads configuration file.
89
def IsPickleFile(f):
90
  """Checks whether a file is using the Pickle format.
109 91

  
110 92
  """
111
  f = open(path, 'r')
93
  magic = f.read(128)
112 94
  try:
113
    loader = cPickle.Unpickler(f)
114
    loader.find_global = _ReadFindGlobal
115
    data = loader.load()
95
    return not re.match('^\s*\{', magic)
116 96
  finally:
117
    f.close()
97
    f.seek(-len(magic), 1)
98
# }}}
118 99

  
119
  return data
120 100

  
121

  
122
# Write config
123
def _WriteFindGlobal(module, name):
124
  """Maps our internal config classes to Ganeti's.
101
def ReadJsonFile(f):
102
  """Reads a JSON file.
125 103

  
126 104
  """
127
  if module == "__main__" and name in WriteMapping:
128
    return WriteMapping[name]
129

  
130
  return _BaseFindGlobal(module, name)
105
  return simplejson.load(f)
131 106

  
132 107

  
133
def WriteConfig(path, data, dry_run):
134
  """Writes the configuration file.
108
def ReadConfig(path):
109
  """Reads configuration file.
135 110

  
136 111
  """
137
  buf = StringIO()
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()
138 120

  
139
  # Write intermediate representation
140
  dumper = cPickle.Pickler(buf, cPickle.HIGHEST_PROTOCOL)
141
  dumper.dump(data)
142
  del dumper
143 121

  
144
  # Convert back to Ganeti objects
145
  buf.seek(0)
146
  loader = cPickle.Unpickler(buf)
147
  loader.find_global = _WriteFindGlobal
148
  data = loader.load()
122
def WriteConfig(path, data):
123
  """Writes the configuration file.
124

  
125
  """
126
  if not options.dry_run:
127
    utils.CreateBackup(path)
149 128

  
150
  # Write target file
151 129
  (fd, name) = tempfile.mkstemp(dir=os.path.dirname(path))
152 130
  f = os.fdopen(fd, 'w')
153 131
  try:
154 132
    try:
155
      dumper = cPickle.Pickler(f, cPickle.HIGHEST_PROTOCOL)
156
      dumper.dump(data)
133
      simplejson.dump(data, f)
157 134
      f.flush()
158
      if dry_run:
135
      if options.dry_run:
159 136
        os.unlink(name)
160 137
      else:
161 138
        os.rename(name, path)
......
175 152

  
176 153
  # Add port pool
177 154
  if 'tcpudp_port_pool' not in cfg['cluster']:
178
    cfg['cluster']['tcpudp_port_pool'] = set()
155
    cfg['cluster']['tcpudp_port_pool'] = []
179 156

  
180 157
  # Add bridge settings
181 158
  if 'default_bridge' not in cfg['cluster']:
......
190 167

  
191 168
# Main program
192 169
if __name__ == "__main__":
170
  program = os.path.basename(sys.argv[0])
171

  
193 172
  # Option parsing
194 173
  parser = optparse.OptionParser()
195 174
  parser.add_option('--dry-run', dest='dry_run',
196 175
                    action="store_true",
197 176
                    help="Try to do the conversion, but don't write "
198 177
                      "output file")
178
  parser.add_option(FORCE_OPT)
199 179
  parser.add_option('--verbose', dest='verbose',
200 180
                    action="store_true",
201 181
                    help="Verbose output")
......
207 187
  else:
208 188
    raise Error("Configuration file not specified")
209 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 AskUser(usertext):
194
      sys.exit(1)
195

  
210 196
  config = ReadConfig(cfg_file)
211 197

  
198
  if options.verbose:
199
    import pprint
200
    print "Before upgrade:"
201
    pprint.pprint(config)
202
    print
203

  
212 204
  UpdateFromVersion2To3(config)
213 205

  
214 206
  if options.verbose:
215
    import pprint
207
    print "After upgrade:"
216 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."
217 216

  
218
  WriteConfig(cfg_file, config, options.dry_run)
217
# vim: set foldmethod=marker :

Also available in: Unified diff