Revision 319856a9

b/INSTALL
36 36
    http://twistedmatrix.com/
37 37
  - Python OpenSSL bindings
38 38
    http://pyopenssl.sourceforge.net/
39
  - simplejson Python module
40
    http://www.undefined.org/python/#simplejson
39 41

  
40 42
For testing, you also need the YAML module for Python (http://pyyaml.org/).
41 43

  
b/daemons/ganeti-noded
97 97

  
98 98
    """
99 99
    bdev_s, size, on_primary, info = params
100
    bdev = objects.ConfigObject.Loads(bdev_s)
100
    bdev = objects.Disk.FromDict(bdev_s)
101 101
    if bdev is None:
102 102
      raise ValueError("can't unserialize data!")
103 103
    return backend.CreateBlockDevice(bdev, size, on_primary, info)
......
108 108

  
109 109
    """
110 110
    bdev_s = params[0]
111
    bdev = objects.ConfigObject.Loads(bdev_s)
111
    bdev = objects.Disk.FromDict(bdev_s)
112 112
    return backend.RemoveBlockDevice(bdev)
113 113

  
114 114
  @staticmethod
......
117 117

  
118 118
    """
119 119
    bdev_s, on_primary = params
120
    bdev = objects.ConfigObject.Loads(bdev_s)
120
    bdev = objects.Disk.FromDict(bdev_s)
121 121
    if bdev is None:
122 122
      raise ValueError("can't unserialize data!")
123 123
    return backend.AssembleBlockDevice(bdev, on_primary)
......
128 128

  
129 129
    """
130 130
    bdev_s = params[0]
131
    bdev = objects.ConfigObject.Loads(bdev_s)
131
    bdev = objects.Disk.FromDict(bdev_s)
132 132
    if bdev is None:
133 133
      raise ValueError("can't unserialize data!")
134 134
    return backend.ShutdownBlockDevice(bdev)
......
142 142

  
143 143
    """
144 144
    bdev_s, ndev_s = params
145
    bdev = objects.ConfigObject.Loads(bdev_s)
146
    ndev = objects.ConfigObject.Loads(ndev_s)
145
    bdev = objects.Disk.FromDict(bdev_s)
146
    ndev = objects.Disk.FromDict(ndev_s)
147 147
    if bdev is None or ndev is None:
148 148
      raise ValueError("can't unserialize data!")
149 149
    return backend.MirrorAddChild(bdev, ndev)
......
157 157

  
158 158
    """
159 159
    bdev_s, ndev_s = params
160
    bdev = objects.ConfigObject.Loads(bdev_s)
161
    ndev = objects.ConfigObject.Loads(ndev_s)
160
    bdev = objects.Disk.FromDict(bdev_s)
161
    ndev = objects.Disk.FromDict(ndev_s)
162 162
    if bdev is None or ndev is None:
163 163
      raise ValueError("can't unserialize data!")
164 164
    return backend.MirrorRemoveChild(bdev, ndev)
......
168 168
    """Return the mirror status for a list of disks.
169 169

  
170 170
    """
171
    disks = [objects.ConfigObject.Loads(dsk_s)
171
    disks = [objects.Disk.FromDict(dsk_s)
172 172
            for dsk_s in params]
173 173
    return backend.GetMirrorStatus(disks)
174 174

  
......
179 179
    This will try to find but not activate a disk.
180 180

  
181 181
    """
182
    disk = objects.ConfigObject.Loads(params[0])
182
    disk = objects.Disk.FromDict(params[0])
183 183
    return backend.FindBlockDevice(disk)
184 184

  
185 185
  @staticmethod
......
191 191
    remove by calling the generic block device remove call.
192 192

  
193 193
    """
194
    cfbd = objects.ConfigObject.Loads(params[0])
194
    cfbd = objects.Disk.FromDict(params[0])
195 195
    return backend.SnapshotBlockDevice(cfbd)
196 196

  
197 197
  # export/import  --------------------------
......
201 201
    """Export a given snapshot.
202 202

  
203 203
    """
204
    disk = objects.ConfigObject.Loads(params[0])
204
    disk = objects.Disk.FromDict(params[0])
205 205
    dest_node = params[1]
206
    instance = objects.ConfigObject.Loads(params[2])
206
    instance = objects.Instance.FromDict(params[2])
207 207
    return backend.ExportSnapshot(disk, dest_node, instance)
208 208

  
209 209
  @staticmethod
......
211 211
    """Expose the finalize export functionality.
212 212

  
213 213
    """
214
    instance = objects.ConfigObject.Loads(params[0])
215
    snap_disks = [objects.ConfigObject.Loads(str_data)
214
    instance = objects.Instance.FromDict(params[0])
215
    snap_disks = [objects.Disk.FromDict(str_data)
216 216
                  for str_data in params[1]]
217 217
    return backend.FinalizeExport(instance, snap_disks)
218 218

  
......
284 284

  
285 285
    """
286 286
    inst_s, os_disk, swap_disk = params
287
    inst = objects.ConfigObject.Loads(inst_s)
287
    inst = objects.Instance.FromDict(inst_s)
288 288
    return backend.AddOSToInstance(inst, os_disk, swap_disk)
289 289

  
290 290
  @staticmethod
......
293 293

  
294 294
    """
295 295
    inst_s, old_name, os_disk, swap_disk = params
296
    inst = objects.ConfigObject.Loads(inst_s)
296
    inst = objects.Instance.FromDict(inst_s)
297 297
    return backend.RunRenameInstance(inst, old_name, os_disk, swap_disk)
298 298

  
299 299
  @staticmethod
......
302 302

  
303 303
    """
304 304
    inst_s, os_disk, swap_disk, src_node, src_image = params
305
    inst = objects.ConfigObject.Loads(inst_s)
305
    inst = objects.Instance.FromDict(inst_s)
306 306
    return backend.ImportOSIntoInstance(inst, os_disk, swap_disk,
307 307
                                        src_node, src_image)
308 308

  
......
311 311
    """Shutdown an instance.
312 312

  
313 313
    """
314
    instance = objects.ConfigObject.Loads(params[0])
314
    instance = objects.Instance.FromDict(params[0])
315 315
    return backend.ShutdownInstance(instance)
316 316

  
317 317
  @staticmethod
......
319 319
    """Start an instance.
320 320

  
321 321
    """
322
    instance = objects.ConfigObject.Loads(params[0])
322
    instance = objects.Instance.FromDict(params[0])
323 323
    extra_args = params[1]
324 324
    return backend.StartInstance(instance, extra_args)
325 325

  
......
432 432
    result = []
433 433
    for data in os_list:
434 434
      if isinstance(data, objects.OS):
435
        result.append(data.Dumps())
435
        result.append(data.ToDict())
436 436
      elif isinstance(data, errors.InvalidOS):
437 437
        result.append(data.args)
438 438
      else:
......
449 449
    """
450 450
    name = params[0]
451 451
    try:
452
      os_obj = backend.OSFromDisk(name).Dumps()
452
      os_obj = backend.OSFromDisk(name).ToDict()
453 453
    except errors.InvalidOS, err:
454 454
      os_obj = err.args
455 455
    return os_obj
b/doc/install.sgml
407 407
          url="http://pyopenssl.sourceforge.net/">Python OpenSSL
408 408
          bindings</ulink></simpara>
409 409
        </listitem>
410
        <listitem>
411
          <simpara><ulink
412
          url="http://www.undefined.org/python/#simplejson">simplejson Python
413
          module</ulink></simpara>
414
        </listitem>
410 415
      </itemizedlist>
411 416

  
412 417
      <para>
b/lib/config.py
21 21

  
22 22
"""Configuration management for Ganeti
23 23

  
24
This module provides the interface to the ganeti cluster configuration.
24
This module provides the interface to the Ganeti cluster configuration.
25 25

  
26
The configuration data is stored on every node but is updated on the master
27
only. After each update, the master distributes the data to the other nodes.
26 28

  
27
The configuration data is stored on every node but is updated on the
28
master only. After each update, the master distributes the data to the
29
other nodes.
30

  
31
Currently the data storage format is pickle as yaml was initially not
32
available, then we used it but it was a memory-eating slow beast, so
33
we reverted to pickle using custom Unpicklers.
29
Currently, the data storage format is JSON. YAML was slow and consuming too
30
much memory.
34 31

  
35 32
"""
36 33

  
......
45 42
from ganeti import rpc
46 43
from ganeti import objects
47 44

  
45

  
48 46
def _my_uuidgen():
49 47
  """Poor-man's uuidgen using the uuidgen binary.
50 48

  
......
497 495
    f = open(self._cfg_file, 'r')
498 496
    try:
499 497
      try:
500
        data = objects.ConfigObject.Load(f)
498
        data = objects.ConfigData.Load(f)
501 499
      except Exception, err:
500
        raise
502 501
        raise errors.ConfigurationError(err)
503 502
    finally:
504 503
      f.close()
b/lib/objects.py
27 27
"""
28 28

  
29 29

  
30
import cPickle
30
import simplejson
31 31
from cStringIO import StringIO
32 32
import ConfigParser
33 33
import re
......
56 56
  __slots__ = []
57 57

  
58 58
  def __init__(self, **kwargs):
59
    for i in kwargs:
60
      setattr(self, i, kwargs[i])
59
    for k, v in kwargs.iteritems():
60
      setattr(self, k, v)
61 61

  
62 62
  def __getattr__(self, name):
63 63
    if name not in self.__slots__:
......
82 82
      if name in self.__slots__:
83 83
        setattr(self, name, state[name])
84 84

  
85
  @staticmethod
86
  def FindGlobal(module, name):
87
    """Function filtering the allowed classes to be un-pickled.
88

  
89
    Currently, we only allow the classes from this module which are
90
    derived from ConfigObject.
91

  
92
    """
93
    # Also support the old module name (ganeti.config)
94
    cls = None
95
    if module == "ganeti.config" or module == "ganeti.objects":
96
      if name == "ConfigData":
97
        cls = ConfigData
98
      elif name == "NIC":
99
        cls = NIC
100
      elif name == "Disk" or name == "BlockDev":
101
        cls = Disk
102
      elif name == "Instance":
103
        cls = Instance
104
      elif name == "OS":
105
        cls = OS
106
      elif name == "Node":
107
        cls = Node
108
      elif name == "Cluster":
109
        cls = Cluster
110
    elif module == "__builtin__":
111
      if name == "set":
112
        cls = set
113
    if cls is None:
114
      raise cPickle.UnpicklingError("Class %s.%s not allowed due to"
115
                                    " security concerns" % (module, name))
116
    return cls
117

  
118 85
  def Dump(self, fobj):
119
    """Dump this instance to a file object.
120

  
121
    Note that we use the HIGHEST_PROTOCOL, as it brings benefits for
122
    the new classes.
86
    """Dump to a file object.
123 87

  
124 88
    """
125
    dumper = cPickle.Pickler(fobj, cPickle.HIGHEST_PROTOCOL)
126
    dumper.dump(self)
89
    simplejson.dump(self.ToDict(), fobj)
127 90

  
128
  @staticmethod
129
  def Load(fobj):
130
    """Unpickle data from the given stream.
131

  
132
    This uses the `FindGlobal` function to filter the allowed classes.
91
  @classmethod
92
  def Load(cls, fobj):
93
    """Load data from the given stream.
133 94

  
134 95
    """
135
    loader = cPickle.Unpickler(fobj)
136
    loader.find_global = ConfigObject.FindGlobal
137
    return loader.load()
96
    return cls.FromDict(simplejson.load(fobj))
138 97

  
139 98
  def Dumps(self):
140
    """Dump this instance and return the string representation."""
99
    """Dump and return the string representation."""
141 100
    buf = StringIO()
142 101
    self.Dump(buf)
143 102
    return buf.getvalue()
144 103

  
145
  @staticmethod
146
  def Loads(data):
104
  @classmethod
105
  def Loads(cls, data):
147 106
    """Load data from a string."""
148
    return ConfigObject.Load(StringIO(data))
107
    return cls.Load(StringIO(data))
149 108

  
150 109
  def ToDict(self):
151 110
    """Convert to a dict holding only standard python types.
......
175 134
    if not isinstance(val, dict):
176 135
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
177 136
                                      " expected dict, got %s" % type(val))
178
    obj = cls(**val)
137
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
138
    obj = cls(**val_str)
179 139
    return obj
180 140

  
181 141
  @staticmethod
......
239 199
    if not isinstance(tag, basestring):
240 200
      raise errors.TagError("Invalid tag type (not a string)")
241 201
    if len(tag) > constants.MAX_TAG_LEN:
242
      raise errors.TagError("Tag too long (>%d)" % constants.MAX_TAG_LEN)
202
      raise errors.TagError("Tag too long (>%d characters)" %
203
                            constants.MAX_TAG_LEN)
243 204
    if not tag:
244 205
      raise errors.TagError("Tags cannot be empty")
245 206
    if not re.match("^[ \w.+*/:-]+$", tag):
......
607 568
    "default_bridge",
608 569
    ]
609 570

  
571
  def ToDict(self):
572
    """Custom function for cluster.
573

  
574
    """
575
    mydict = super(TaggableObject, self).ToDict()
576
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
577
    return mydict
578

  
579
  @classmethod
580
  def FromDict(cls, val):
581
    """Custom function for cluster.
582

  
583
    """
584
    obj = super(TaggableObject, cls).FromDict(val)
585
    if not isinstance(obj.tcpudp_port_pool, set):
586
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
587
    return obj
588

  
610 589

  
611 590
class SerializableConfigParser(ConfigParser.SafeConfigParser):
612 591
  """Simple wrapper over ConfigParse that allows serialization.
b/lib/rpc.py
293 293
  This is a single-node call.
294 294

  
295 295
  """
296
  c = Client("instance_start", [instance.Dumps(), extra_args])
296
  c = Client("instance_start", [instance.ToDict(), extra_args])
297 297
  c.connect(node)
298 298
  c.run()
299 299
  return c.getresult().get(node, False)
......
305 305
  This is a single-node call.
306 306

  
307 307
  """
308
  c = Client("instance_shutdown", [instance.Dumps()])
308
  c = Client("instance_shutdown", [instance.ToDict()])
309 309
  c.connect(node)
310 310
  c.run()
311 311
  return c.getresult().get(node, False)
......
317 317
  This is a single-node call.
318 318

  
319 319
  """
320
  params = [inst.Dumps(), osdev, swapdev]
320
  params = [inst.ToDict(), osdev, swapdev]
321 321
  c = Client("instance_os_add", params)
322 322
  c.connect(node)
323 323
  c.run()
......
330 330
  This is a single-node call.
331 331

  
332 332
  """
333
  params = [inst.Dumps(), old_name, osdev, swapdev]
333
  params = [inst.ToDict(), old_name, osdev, swapdev]
334 334
  c = Client("instance_run_rename", params)
335 335
  c.connect(node)
336 336
  c.run()
......
471 471
  This is a single-node call.
472 472

  
473 473
  """
474
  params = [bdev.Dumps(), size, on_primary, info]
474
  params = [bdev.ToDict(), size, on_primary, info]
475 475
  c = Client("blockdev_create", params)
476 476
  c.connect(node)
477 477
  c.run()
......
484 484
  This is a single-node call.
485 485

  
486 486
  """
487
  c = Client("blockdev_remove", [bdev.Dumps()])
487
  c = Client("blockdev_remove", [bdev.ToDict()])
488 488
  c.connect(node)
489 489
  c.run()
490 490
  return c.getresult().get(node, False)
......
496 496
  This is a single-node call.
497 497

  
498 498
  """
499
  params = [disk.Dumps(), on_primary]
499
  params = [disk.ToDict(), on_primary]
500 500
  c = Client("blockdev_assemble", params)
501 501
  c.connect(node)
502 502
  c.run()
......
509 509
  This is a single-node call.
510 510

  
511 511
  """
512
  c = Client("blockdev_shutdown", [disk.Dumps()])
512
  c = Client("blockdev_shutdown", [disk.ToDict()])
513 513
  c.connect(node)
514 514
  c.run()
515 515
  return c.getresult().get(node, False)
......
521 521
  This is a single-node call.
522 522

  
523 523
  """
524
  params = [bdev.Dumps(), ndev.Dumps()]
524
  params = [bdev.ToDict(), ndev.ToDict()]
525 525
  c = Client("blockdev_addchild", params)
526 526
  c.connect(node)
527 527
  c.run()
......
534 534
  This is a single-node call.
535 535

  
536 536
  """
537
  params = [bdev.Dumps(), ndev.Dumps()]
537
  params = [bdev.ToDict(), ndev.ToDict()]
538 538
  c = Client("blockdev_removechild", params)
539 539
  c.connect(node)
540 540
  c.run()
......
547 547
  This is a single-node call.
548 548

  
549 549
  """
550
  params = [dsk.Dumps() for dsk in disks]
550
  params = [dsk.ToDict() for dsk in disks]
551 551
  c = Client("blockdev_getmirrorstatus", params)
552 552
  c.connect(node)
553 553
  c.run()
......
560 560
  This is a single-node call.
561 561

  
562 562
  """
563
  c = Client("blockdev_find", [disk.Dumps()])
563
  c = Client("blockdev_find", [disk.ToDict()])
564 564
  c.connect(node)
565 565
  c.run()
566 566
  return c.getresult().get(node, False)
......
605 605
    if result[node_name]:
606 606
      for data in result[node_name]:
607 607
        if data:
608
          if isinstance(data, basestring):
609
            nr.append(objects.ConfigObject.Loads(data))
608
          if isinstance(data, dict):
609
            nr.append(objects.OS.FromDict(data))
610 610
          elif isinstance(data, tuple) and len(data) == 2:
611 611
            nr.append(errors.InvalidOS(data[0], data[1]))
612 612
          else:
......
629 629
  new_result = {}
630 630
  for node_name in result:
631 631
    data = result[node_name]
632
    if isinstance(data, basestring):
633
      new_result[node_name] = objects.ConfigObject.Loads(data)
632
    if isinstance(data, dict):
633
      new_result[node_name] = objects.OS.FromDict(data)
634 634
    elif isinstance(data, tuple) and len(data) == 2:
635 635
      new_result[node_name] = errors.InvalidOS(data[0], data[1])
636 636
    else:
......
662 662
  This is a single-node call.
663 663

  
664 664
  """
665
  c = Client("blockdev_snapshot", [cf_bdev.Dumps()])
665
  c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
666 666
  c.connect(node)
667 667
  c.run()
668 668
  return c.getresult().get(node, False)
......
674 674
  This is a single-node call.
675 675

  
676 676
  """
677
  params = [snap_bdev.Dumps(), dest_node, instance.Dumps()]
677
  params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
678 678
  c = Client("snapshot_export", params)
679 679
  c.connect(node)
680 680
  c.run()
......
691 691
  """
692 692
  flat_disks = []
693 693
  for disk in snap_disks:
694
    flat_disks.append(disk.Dumps())
695
  params = [instance.Dumps(), flat_disks]
694
    flat_disks.append(disk.ToDict())
695
  params = [instance.ToDict(), flat_disks]
696 696
  c = Client("finalize_export", params)
697 697
  c.connect(node)
698 698
  c.run()
......
720 720
  This is a single-node call.
721 721

  
722 722
  """
723
  params = [inst.Dumps(), osdev, swapdev, src_node, src_image]
723
  params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
724 724
  c = Client("instance_os_import", params)
725 725
  c.connect(node)
726 726
  c.run()
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