Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ a805ec18

History | View | Annotate | Download (24.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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
"""Transportable objects for Ganeti.
23

24
This module provides small, mostly data-only objects which are safe to
25
pass to and from external parties.
26

27
"""
28

    
29

    
30
import ConfigParser
31
import re
32
import copy
33
from cStringIO import StringIO
34

    
35
from ganeti import errors
36
from ganeti import constants
37

    
38

    
39
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
40
           "OS", "Node", "Cluster"]
41

    
42

    
43
class ConfigObject(object):
44
  """A generic config object.
45

46
  It has the following properties:
47

48
    - provides somewhat safe recursive unpickling and pickling for its classes
49
    - unset attributes which are defined in slots are always returned
50
      as None instead of raising an error
51

52
  Classes derived from this must always declare __slots__ (we use many
53
  config objects and the memory reduction is useful)
54

55
  """
56
  __slots__ = []
57

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

    
62
  def __getattr__(self, name):
63
    if name not in self.__slots__:
64
      raise AttributeError("Invalid object attribute %s.%s" %
65
                           (type(self).__name__, name))
66
    return None
67

    
68
  def __setitem__(self, key, value):
69
    if key not in self.__slots__:
70
      raise KeyError(key)
71
    setattr(self, key, value)
72

    
73
  def __getstate__(self):
74
    state = {}
75
    for name in self.__slots__:
76
      if hasattr(self, name):
77
        state[name] = getattr(self, name)
78
    return state
79

    
80
  def __setstate__(self, state):
81
    for name in state:
82
      if name in self.__slots__:
83
        setattr(self, name, state[name])
84

    
85
  def ToDict(self):
86
    """Convert to a dict holding only standard python types.
87

88
    The generic routine just dumps all of this object's attributes in
89
    a dict. It does not work if the class has children who are
90
    ConfigObjects themselves (e.g. the nics list in an Instance), in
91
    which case the object should subclass the function in order to
92
    make sure all objects returned are only standard python types.
93

94
    """
95
    return dict([(k, getattr(self, k, None)) for k in self.__slots__])
96

    
97
  @classmethod
98
  def FromDict(cls, val):
99
    """Create an object from a dictionary.
100

101
    This generic routine takes a dict, instantiates a new instance of
102
    the given class, and sets attributes based on the dict content.
103

104
    As for `ToDict`, this does not work if the class has children
105
    who are ConfigObjects themselves (e.g. the nics list in an
106
    Instance), in which case the object should subclass the function
107
    and alter the objects.
108

109
    """
110
    if not isinstance(val, dict):
111
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
112
                                      " expected dict, got %s" % type(val))
113
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
114
    obj = cls(**val_str)
115
    return obj
116

    
117
  @staticmethod
118
  def _ContainerToDicts(container):
119
    """Convert the elements of a container to standard python types.
120

121
    This method converts a container with elements derived from
122
    ConfigData to standard python types. If the container is a dict,
123
    we don't touch the keys, only the values.
124

125
    """
126
    if isinstance(container, dict):
127
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
128
    elif isinstance(container, (list, tuple, set, frozenset)):
129
      ret = [elem.ToDict() for elem in container]
130
    else:
131
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
132
                      type(container))
133
    return ret
134

    
135
  @staticmethod
136
  def _ContainerFromDicts(source, c_type, e_type):
137
    """Convert a container from standard python types.
138

139
    This method converts a container with standard python types to
140
    ConfigData objects. If the container is a dict, we don't touch the
141
    keys, only the values.
142

143
    """
144
    if not isinstance(c_type, type):
145
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
146
                      " not a type" % type(c_type))
147
    if c_type is dict:
148
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
149
    elif c_type in (list, tuple, set, frozenset):
150
      ret = c_type([e_type.FromDict(elem) for elem in source])
151
    else:
152
      raise TypeError("Invalid container type %s passed to"
153
                      " _ContainerFromDicts" % c_type)
154
    return ret
155

    
156
  def Copy(self):
157
    """Makes a deep copy of the current object and its children.
158

159
    """
160
    dict_form = self.ToDict()
161
    clone_obj = self.__class__.FromDict(dict_form)
162
    return clone_obj
163

    
164
  def __repr__(self):
165
    """Implement __repr__ for ConfigObjects."""
166
    return repr(self.ToDict())
167

    
168

    
169
class TaggableObject(ConfigObject):
170
  """An generic class supporting tags.
171

172
  """
173
  __slots__ = ConfigObject.__slots__ + ["tags"]
174

    
175
  @staticmethod
176
  def ValidateTag(tag):
177
    """Check if a tag is valid.
178

179
    If the tag is invalid, an errors.TagError will be raised. The
180
    function has no return value.
181

182
    """
183
    if not isinstance(tag, basestring):
184
      raise errors.TagError("Invalid tag type (not a string)")
185
    if len(tag) > constants.MAX_TAG_LEN:
186
      raise errors.TagError("Tag too long (>%d characters)" %
187
                            constants.MAX_TAG_LEN)
188
    if not tag:
189
      raise errors.TagError("Tags cannot be empty")
190
    if not re.match("^[\w.+*/:-]+$", tag):
191
      raise errors.TagError("Tag contains invalid characters")
192

    
193
  def GetTags(self):
194
    """Return the tags list.
195

196
    """
197
    tags = getattr(self, "tags", None)
198
    if tags is None:
199
      tags = self.tags = set()
200
    return tags
201

    
202
  def AddTag(self, tag):
203
    """Add a new tag.
204

205
    """
206
    self.ValidateTag(tag)
207
    tags = self.GetTags()
208
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
209
      raise errors.TagError("Too many tags")
210
    self.GetTags().add(tag)
211

    
212
  def RemoveTag(self, tag):
213
    """Remove a tag.
214

215
    """
216
    self.ValidateTag(tag)
217
    tags = self.GetTags()
218
    try:
219
      tags.remove(tag)
220
    except KeyError:
221
      raise errors.TagError("Tag not found")
222

    
223
  def ToDict(self):
224
    """Taggable-object-specific conversion to standard python types.
225

226
    This replaces the tags set with a list.
227

228
    """
229
    bo = super(TaggableObject, self).ToDict()
230

    
231
    tags = bo.get("tags", None)
232
    if isinstance(tags, set):
233
      bo["tags"] = list(tags)
234
    return bo
235

    
236
  @classmethod
237
  def FromDict(cls, val):
238
    """Custom function for instances.
239

240
    """
241
    obj = super(TaggableObject, cls).FromDict(val)
242
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
243
      obj.tags = set(obj.tags)
244
    return obj
245

    
246

    
247
class ConfigData(ConfigObject):
248
  """Top-level config object."""
249
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
250

    
251
  def ToDict(self):
252
    """Custom function for top-level config data.
253

254
    This just replaces the list of instances, nodes and the cluster
255
    with standard python types.
256

257
    """
258
    mydict = super(ConfigData, self).ToDict()
259
    mydict["cluster"] = mydict["cluster"].ToDict()
260
    for key in "nodes", "instances":
261
      mydict[key] = self._ContainerToDicts(mydict[key])
262

    
263
    return mydict
264

    
265
  @classmethod
266
  def FromDict(cls, val):
267
    """Custom function for top-level config data
268

269
    """
270
    obj = super(ConfigData, cls).FromDict(val)
271
    obj.cluster = Cluster.FromDict(obj.cluster)
272
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
273
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
274
    return obj
275

    
276

    
277
class NIC(ConfigObject):
278
  """Config object representing a network card."""
279
  __slots__ = ["mac", "ip", "bridge"]
280

    
281

    
282
class Disk(ConfigObject):
283
  """Config object representing a block device."""
284
  __slots__ = ["dev_type", "logical_id", "physical_id",
285
               "children", "iv_name", "size", "mode"]
286

    
287
  def CreateOnSecondary(self):
288
    """Test if this device needs to be created on a secondary node."""
289
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
290

    
291
  def AssembleOnSecondary(self):
292
    """Test if this device needs to be assembled on a secondary node."""
293
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
294

    
295
  def OpenOnSecondary(self):
296
    """Test if this device needs to be opened on a secondary node."""
297
    return self.dev_type in (constants.LD_LV,)
298

    
299
  def StaticDevPath(self):
300
    """Return the device path if this device type has a static one.
301

302
    Some devices (LVM for example) live always at the same /dev/ path,
303
    irrespective of their status. For such devices, we return this
304
    path, for others we return None.
305

306
    """
307
    if self.dev_type == constants.LD_LV:
308
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
309
    return None
310

    
311
  def ChildrenNeeded(self):
312
    """Compute the needed number of children for activation.
313

314
    This method will return either -1 (all children) or a positive
315
    number denoting the minimum number of children needed for
316
    activation (only mirrored devices will usually return >=0).
317

318
    Currently, only DRBD8 supports diskless activation (therefore we
319
    return 0), for all other we keep the previous semantics and return
320
    -1.
321

322
    """
323
    if self.dev_type == constants.LD_DRBD8:
324
      return 0
325
    return -1
326

    
327
  def GetNodes(self, node):
328
    """This function returns the nodes this device lives on.
329

330
    Given the node on which the parent of the device lives on (or, in
331
    case of a top-level device, the primary node of the devices'
332
    instance), this function will return a list of nodes on which this
333
    devices needs to (or can) be assembled.
334

335
    """
336
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
337
      result = [node]
338
    elif self.dev_type in constants.LDS_DRBD:
339
      result = [self.logical_id[0], self.logical_id[1]]
340
      if node not in result:
341
        raise errors.ConfigurationError("DRBD device passed unknown node")
342
    else:
343
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
344
    return result
345

    
346
  def ComputeNodeTree(self, parent_node):
347
    """Compute the node/disk tree for this disk and its children.
348

349
    This method, given the node on which the parent disk lives, will
350
    return the list of all (node, disk) pairs which describe the disk
351
    tree in the most compact way. For example, a drbd/lvm stack
352
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
353
    which represents all the top-level devices on the nodes.
354

355
    """
356
    my_nodes = self.GetNodes(parent_node)
357
    result = [(node, self) for node in my_nodes]
358
    if not self.children:
359
      # leaf device
360
      return result
361
    for node in my_nodes:
362
      for child in self.children:
363
        child_result = child.ComputeNodeTree(node)
364
        if len(child_result) == 1:
365
          # child (and all its descendants) is simple, doesn't split
366
          # over multiple hosts, so we don't need to describe it, our
367
          # own entry for this node describes it completely
368
          continue
369
        else:
370
          # check if child nodes differ from my nodes; note that
371
          # subdisk can differ from the child itself, and be instead
372
          # one of its descendants
373
          for subnode, subdisk in child_result:
374
            if subnode not in my_nodes:
375
              result.append((subnode, subdisk))
376
            # otherwise child is under our own node, so we ignore this
377
            # entry (but probably the other results in the list will
378
            # be different)
379
    return result
380

    
381
  def RecordGrow(self, amount):
382
    """Update the size of this disk after growth.
383

384
    This method recurses over the disks's children and updates their
385
    size correspondigly. The method needs to be kept in sync with the
386
    actual algorithms from bdev.
387

388
    """
389
    if self.dev_type == constants.LD_LV:
390
      self.size += amount
391
    elif self.dev_type == constants.LD_DRBD8:
392
      if self.children:
393
        self.children[0].RecordGrow(amount)
394
      self.size += amount
395
    else:
396
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
397
                                   " disk type %s" % self.dev_type)
398

    
399
  def UnsetSize(self):
400
    """Sets recursively the size to zero for the disk and its children.
401

402
    """
403
    if self.children:
404
      for child in self.children:
405
        child.UnsetSize()
406
    self.size = 0
407

    
408
  def SetPhysicalID(self, target_node, nodes_ip):
409
    """Convert the logical ID to the physical ID.
410

411
    This is used only for drbd, which needs ip/port configuration.
412

413
    The routine descends down and updates its children also, because
414
    this helps when the only the top device is passed to the remote
415
    node.
416

417
    Arguments:
418
      - target_node: the node we wish to configure for
419
      - nodes_ip: a mapping of node name to ip
420

421
    The target_node must exist in in nodes_ip, and must be one of the
422
    nodes in the logical ID for each of the DRBD devices encountered
423
    in the disk tree.
424

425
    """
426
    if self.children:
427
      for child in self.children:
428
        child.SetPhysicalID(target_node, nodes_ip)
429

    
430
    if self.logical_id is None and self.physical_id is not None:
431
      return
432
    if self.dev_type in constants.LDS_DRBD:
433
      pnode, snode, port, pminor, sminor, secret = self.logical_id
434
      if target_node not in (pnode, snode):
435
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
436
                                        target_node)
437
      pnode_ip = nodes_ip.get(pnode, None)
438
      snode_ip = nodes_ip.get(snode, None)
439
      if pnode_ip is None or snode_ip is None:
440
        raise errors.ConfigurationError("Can't find primary or secondary node"
441
                                        " for %s" % str(self))
442
      p_data = (pnode_ip, port)
443
      s_data = (snode_ip, port)
444
      if pnode == target_node:
445
        self.physical_id = p_data + s_data + (pminor, secret)
446
      else: # it must be secondary, we tested above
447
        self.physical_id = s_data + p_data + (sminor, secret)
448
    else:
449
      self.physical_id = self.logical_id
450
    return
451

    
452
  def ToDict(self):
453
    """Disk-specific conversion to standard python types.
454

455
    This replaces the children lists of objects with lists of
456
    standard python types.
457

458
    """
459
    bo = super(Disk, self).ToDict()
460

    
461
    for attr in ("children",):
462
      alist = bo.get(attr, None)
463
      if alist:
464
        bo[attr] = self._ContainerToDicts(alist)
465
    return bo
466

    
467
  @classmethod
468
  def FromDict(cls, val):
469
    """Custom function for Disks
470

471
    """
472
    obj = super(Disk, cls).FromDict(val)
473
    if obj.children:
474
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
475
    if obj.logical_id and isinstance(obj.logical_id, list):
476
      obj.logical_id = tuple(obj.logical_id)
477
    if obj.physical_id and isinstance(obj.physical_id, list):
478
      obj.physical_id = tuple(obj.physical_id)
479
    if obj.dev_type in constants.LDS_DRBD:
480
      # we need a tuple of length six here
481
      if len(obj.logical_id) < 6:
482
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
483
    return obj
484

    
485
  def __str__(self):
486
    """Custom str() formatter for disks.
487

488
    """
489
    if self.dev_type == constants.LD_LV:
490
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
491
    elif self.dev_type in constants.LDS_DRBD:
492
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
493
      val = "<DRBD8("
494
      if self.physical_id is None:
495
        phy = "unconfigured"
496
      else:
497
        phy = ("configured as %s:%s %s:%s" %
498
               (self.physical_id[0], self.physical_id[1],
499
                self.physical_id[2], self.physical_id[3]))
500

    
501
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
502
              (node_a, minor_a, node_b, minor_b, port, phy))
503
      if self.children and self.children.count(None) == 0:
504
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
505
      else:
506
        val += "no local storage"
507
    else:
508
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
509
             (self.dev_type, self.logical_id, self.physical_id, self.children))
510
    if self.iv_name is None:
511
      val += ", not visible"
512
    else:
513
      val += ", visible as /dev/%s" % self.iv_name
514
    if isinstance(self.size, int):
515
      val += ", size=%dm)>" % self.size
516
    else:
517
      val += ", size='%s')>" % (self.size,)
518
    return val
519

    
520
  def Verify(self):
521
    """Checks that this disk is correctly configured.
522

523
    """
524
    errors = []
525
    if self.mode not in constants.DISK_ACCESS_SET:
526
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
527
    return errors
528

    
529

    
530
class Instance(TaggableObject):
531
  """Config object representing an instance."""
532
  __slots__ = TaggableObject.__slots__ + [
533
    "name",
534
    "primary_node",
535
    "os",
536
    "hypervisor",
537
    "hvparams",
538
    "beparams",
539
    "admin_up",
540
    "nics",
541
    "disks",
542
    "disk_template",
543
    "network_port",
544
    "serial_no",
545
    ]
546

    
547
  def _ComputeSecondaryNodes(self):
548
    """Compute the list of secondary nodes.
549

550
    This is a simple wrapper over _ComputeAllNodes.
551

552
    """
553
    all_nodes = set(self._ComputeAllNodes())
554
    all_nodes.discard(self.primary_node)
555
    return tuple(all_nodes)
556

    
557
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
558
                             "List of secondary nodes")
559

    
560
  def _ComputeAllNodes(self):
561
    """Compute the list of all nodes.
562

563
    Since the data is already there (in the drbd disks), keeping it as
564
    a separate normal attribute is redundant and if not properly
565
    synchronised can cause problems. Thus it's better to compute it
566
    dynamically.
567

568
    """
569
    def _Helper(nodes, device):
570
      """Recursively computes nodes given a top device."""
571
      if device.dev_type in constants.LDS_DRBD:
572
        nodea, nodeb = device.logical_id[:2]
573
        nodes.add(nodea)
574
        nodes.add(nodeb)
575
      if device.children:
576
        for child in device.children:
577
          _Helper(nodes, child)
578

    
579
    all_nodes = set()
580
    all_nodes.add(self.primary_node)
581
    for device in self.disks:
582
      _Helper(all_nodes, device)
583
    return tuple(all_nodes)
584

    
585
  all_nodes = property(_ComputeAllNodes, None, None,
586
                       "List of all nodes of the instance")
587

    
588
  def MapLVsByNode(self, lvmap=None, devs=None, node=None):
589
    """Provide a mapping of nodes to LVs this instance owns.
590

591
    This function figures out what logical volumes should belong on
592
    which nodes, recursing through a device tree.
593

594
    @param lvmap: optional dictionary to receive the
595
        'node' : ['lv', ...] data.
596

597
    @return: None if lvmap arg is given, otherwise, a dictionary
598
        of the form { 'nodename' : ['volume1', 'volume2', ...], ... }
599

600
    """
601
    if node == None:
602
      node = self.primary_node
603

    
604
    if lvmap is None:
605
      lvmap = { node : [] }
606
      ret = lvmap
607
    else:
608
      if not node in lvmap:
609
        lvmap[node] = []
610
      ret = None
611

    
612
    if not devs:
613
      devs = self.disks
614

    
615
    for dev in devs:
616
      if dev.dev_type == constants.LD_LV:
617
        lvmap[node].append(dev.logical_id[1])
618

    
619
      elif dev.dev_type in constants.LDS_DRBD:
620
        if dev.children:
621
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
622
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
623

    
624
      elif dev.children:
625
        self.MapLVsByNode(lvmap, dev.children, node)
626

    
627
    return ret
628

    
629
  def FindDisk(self, idx):
630
    """Find a disk given having a specified index.
631

632
    This is just a wrapper that does validation of the index.
633

634
    @type idx: int
635
    @param idx: the disk index
636
    @rtype: L{Disk}
637
    @return: the corresponding disk
638
    @raise errors.OpPrereqError: when the given index is not valid
639

640
    """
641
    try:
642
      idx = int(idx)
643
      return self.disks[idx]
644
    except ValueError, err:
645
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
646
    except IndexError:
647
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
648
                                 " 0 to %d" % (idx, len(self.disks)))
649

    
650
  def ToDict(self):
651
    """Instance-specific conversion to standard python types.
652

653
    This replaces the children lists of objects with lists of standard
654
    python types.
655

656
    """
657
    bo = super(Instance, self).ToDict()
658

    
659
    for attr in "nics", "disks":
660
      alist = bo.get(attr, None)
661
      if alist:
662
        nlist = self._ContainerToDicts(alist)
663
      else:
664
        nlist = []
665
      bo[attr] = nlist
666
    return bo
667

    
668
  @classmethod
669
  def FromDict(cls, val):
670
    """Custom function for instances.
671

672
    """
673
    obj = super(Instance, cls).FromDict(val)
674
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
675
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
676
    return obj
677

    
678

    
679
class OS(ConfigObject):
680
  """Config object representing an operating system."""
681
  __slots__ = [
682
    "name",
683
    "path",
684
    "status",
685
    "api_versions",
686
    "create_script",
687
    "export_script",
688
    "import_script",
689
    "rename_script",
690
    ]
691

    
692
  @classmethod
693
  def FromInvalidOS(cls, err):
694
    """Create an OS from an InvalidOS error.
695

696
    This routine knows how to convert an InvalidOS error to an OS
697
    object representing the broken OS with a meaningful error message.
698

699
    """
700
    if not isinstance(err, errors.InvalidOS):
701
      raise errors.ProgrammerError("Trying to initialize an OS from an"
702
                                   " invalid object of type %s" % type(err))
703

    
704
    return cls(name=err.args[0], path=err.args[1], status=err.args[2])
705

    
706
  def __nonzero__(self):
707
    return self.status == constants.OS_VALID_STATUS
708

    
709
  __bool__ = __nonzero__
710

    
711

    
712
class Node(TaggableObject):
713
  """Config object representing a node."""
714
  __slots__ = TaggableObject.__slots__ + [
715
    "name",
716
    "primary_ip",
717
    "secondary_ip",
718
    "serial_no",
719
    "master_candidate",
720
    "offline",
721
    "drained",
722
    ]
723

    
724

    
725
class Cluster(TaggableObject):
726
  """Config object representing the cluster."""
727
  __slots__ = TaggableObject.__slots__ + [
728
    "serial_no",
729
    "rsahostkeypub",
730
    "highest_used_port",
731
    "tcpudp_port_pool",
732
    "mac_prefix",
733
    "volume_group_name",
734
    "default_bridge",
735
    "default_hypervisor",
736
    "master_node",
737
    "master_ip",
738
    "master_netdev",
739
    "cluster_name",
740
    "file_storage_dir",
741
    "enabled_hypervisors",
742
    "hvparams",
743
    "beparams",
744
    "candidate_pool_size",
745
    ]
746

    
747
  def ToDict(self):
748
    """Custom function for cluster.
749

750
    """
751
    mydict = super(Cluster, self).ToDict()
752
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
753
    return mydict
754

    
755
  @classmethod
756
  def FromDict(cls, val):
757
    """Custom function for cluster.
758

759
    """
760
    obj = super(Cluster, cls).FromDict(val)
761
    if not isinstance(obj.tcpudp_port_pool, set):
762
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
763
    return obj
764

    
765
  @staticmethod
766
  def FillDict(defaults_dict, custom_dict):
767
    """Basic function to apply settings on top a default dict.
768

769
    @type defaults_dict: dict
770
    @param defaults_dict: dictionary holding the default values
771
    @type custom_dict: dict
772
    @param custom_dict: dictionary holding customized value
773
    @rtype: dict
774
    @return: dict with the 'full' values
775

776
    """
777
    ret_dict = copy.deepcopy(defaults_dict)
778
    ret_dict.update(custom_dict)
779
    return ret_dict
780

    
781
  def FillHV(self, instance):
782
    """Fill an instance's hvparams dict.
783

784
    @type instance: L{objects.Instance}
785
    @param instance: the instance parameter to fill
786
    @rtype: dict
787
    @return: a copy of the instance's hvparams with missing keys filled from
788
        the cluster defaults
789

790
    """
791
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
792
                         instance.hvparams)
793

    
794
  def FillBE(self, instance):
795
    """Fill an instance's beparams dict.
796

797
    @type instance: L{objects.Instance}
798
    @param instance: the instance parameter to fill
799
    @rtype: dict
800
    @return: a copy of the instance's beparams with missing keys filled from
801
        the cluster defaults
802

803
    """
804
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
805
                         instance.beparams)
806

    
807

    
808
class SerializableConfigParser(ConfigParser.SafeConfigParser):
809
  """Simple wrapper over ConfigParse that allows serialization.
810

811
  This class is basically ConfigParser.SafeConfigParser with two
812
  additional methods that allow it to serialize/unserialize to/from a
813
  buffer.
814

815
  """
816
  def Dumps(self):
817
    """Dump this instance and return the string representation."""
818
    buf = StringIO()
819
    self.write(buf)
820
    return buf.getvalue()
821

    
822
  @staticmethod
823
  def Loads(data):
824
    """Load data from a string."""
825
    buf = StringIO(data)
826
    cfp = SerializableConfigParser()
827
    cfp.readfp(buf)
828
    return cfp