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 |
|
|
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 |
|
|
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