Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ af64c0ea

History | View | Annotate | Download (24 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 __repr__(self):
157
    """Implement __repr__ for ConfigObjects."""
158
    return repr(self.ToDict())
159

    
160

    
161
class TaggableObject(ConfigObject):
162
  """An generic class supporting tags.
163

164
  """
165
  __slots__ = ConfigObject.__slots__ + ["tags"]
166

    
167
  @staticmethod
168
  def ValidateTag(tag):
169
    """Check if a tag is valid.
170

171
    If the tag is invalid, an errors.TagError will be raised. The
172
    function has no return value.
173

174
    """
175
    if not isinstance(tag, basestring):
176
      raise errors.TagError("Invalid tag type (not a string)")
177
    if len(tag) > constants.MAX_TAG_LEN:
178
      raise errors.TagError("Tag too long (>%d characters)" %
179
                            constants.MAX_TAG_LEN)
180
    if not tag:
181
      raise errors.TagError("Tags cannot be empty")
182
    if not re.match("^[ \w.+*/:-]+$", tag):
183
      raise errors.TagError("Tag contains invalid characters")
184

    
185
  def GetTags(self):
186
    """Return the tags list.
187

188
    """
189
    tags = getattr(self, "tags", None)
190
    if tags is None:
191
      tags = self.tags = set()
192
    return tags
193

    
194
  def AddTag(self, tag):
195
    """Add a new tag.
196

197
    """
198
    self.ValidateTag(tag)
199
    tags = self.GetTags()
200
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
201
      raise errors.TagError("Too many tags")
202
    self.GetTags().add(tag)
203

    
204
  def RemoveTag(self, tag):
205
    """Remove a tag.
206

207
    """
208
    self.ValidateTag(tag)
209
    tags = self.GetTags()
210
    try:
211
      tags.remove(tag)
212
    except KeyError:
213
      raise errors.TagError("Tag not found")
214

    
215
  def ToDict(self):
216
    """Taggable-object-specific conversion to standard python types.
217

218
    This replaces the tags set with a list.
219

220
    """
221
    bo = super(TaggableObject, self).ToDict()
222

    
223
    tags = bo.get("tags", None)
224
    if isinstance(tags, set):
225
      bo["tags"] = list(tags)
226
    return bo
227

    
228
  @classmethod
229
  def FromDict(cls, val):
230
    """Custom function for instances.
231

232
    """
233
    obj = super(TaggableObject, cls).FromDict(val)
234
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
235
      obj.tags = set(obj.tags)
236
    return obj
237

    
238

    
239
class ConfigData(ConfigObject):
240
  """Top-level config object."""
241
  __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
242

    
243
  def ToDict(self):
244
    """Custom function for top-level config data.
245

246
    This just replaces the list of instances, nodes and the cluster
247
    with standard python types.
248

249
    """
250
    mydict = super(ConfigData, self).ToDict()
251
    mydict["cluster"] = mydict["cluster"].ToDict()
252
    for key in "nodes", "instances":
253
      mydict[key] = self._ContainerToDicts(mydict[key])
254

    
255
    return mydict
256

    
257
  @classmethod
258
  def FromDict(cls, val):
259
    """Custom function for top-level config data
260

261
    """
262
    obj = super(ConfigData, cls).FromDict(val)
263
    obj.cluster = Cluster.FromDict(obj.cluster)
264
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
265
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
266
    return obj
267

    
268

    
269
class NIC(ConfigObject):
270
  """Config object representing a network card."""
271
  __slots__ = ["mac", "ip", "bridge"]
272

    
273

    
274
class Disk(ConfigObject):
275
  """Config object representing a block device."""
276
  __slots__ = ["dev_type", "logical_id", "physical_id",
277
               "children", "iv_name", "size", "mode"]
278

    
279
  def CreateOnSecondary(self):
280
    """Test if this device needs to be created on a secondary node."""
281
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
282

    
283
  def AssembleOnSecondary(self):
284
    """Test if this device needs to be assembled on a secondary node."""
285
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
286

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

    
291
  def StaticDevPath(self):
292
    """Return the device path if this device type has a static one.
293

294
    Some devices (LVM for example) live always at the same /dev/ path,
295
    irrespective of their status. For such devices, we return this
296
    path, for others we return None.
297

298
    """
299
    if self.dev_type == constants.LD_LV:
300
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
301
    return None
302

    
303
  def ChildrenNeeded(self):
304
    """Compute the needed number of children for activation.
305

306
    This method will return either -1 (all children) or a positive
307
    number denoting the minimum number of children needed for
308
    activation (only mirrored devices will usually return >=0).
309

310
    Currently, only DRBD8 supports diskless activation (therefore we
311
    return 0), for all other we keep the previous semantics and return
312
    -1.
313

314
    """
315
    if self.dev_type == constants.LD_DRBD8:
316
      return 0
317
    return -1
318

    
319
  def GetNodes(self, node):
320
    """This function returns the nodes this device lives on.
321

322
    Given the node on which the parent of the device lives on (or, in
323
    case of a top-level device, the primary node of the devices'
324
    instance), this function will return a list of nodes on which this
325
    devices needs to (or can) be assembled.
326

327
    """
328
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
329
      result = [node]
330
    elif self.dev_type in constants.LDS_DRBD:
331
      result = [self.logical_id[0], self.logical_id[1]]
332
      if node not in result:
333
        raise errors.ConfigurationError("DRBD device passed unknown node")
334
    else:
335
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
336
    return result
337

    
338
  def ComputeNodeTree(self, parent_node):
339
    """Compute the node/disk tree for this disk and its children.
340

341
    This method, given the node on which the parent disk lives, will
342
    return the list of all (node, disk) pairs which describe the disk
343
    tree in the most compact way. For example, a drbd/lvm stack
344
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
345
    which represents all the top-level devices on the nodes.
346

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

    
373
  def RecordGrow(self, amount):
374
    """Update the size of this disk after growth.
375

376
    This method recurses over the disks's children and updates their
377
    size correspondigly. The method needs to be kept in sync with the
378
    actual algorithms from bdev.
379

380
    """
381
    if self.dev_type == constants.LD_LV:
382
      self.size += amount
383
    elif self.dev_type == constants.LD_DRBD8:
384
      if self.children:
385
        self.children[0].RecordGrow(amount)
386
      self.size += amount
387
    else:
388
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
389
                                   " disk type %s" % self.dev_type)
390

    
391
  def SetPhysicalID(self, target_node, nodes_ip):
392
    """Convert the logical ID to the physical ID.
393

394
    This is used only for drbd, which needs ip/port configuration.
395

396
    The routine descends down and updates its children also, because
397
    this helps when the only the top device is passed to the remote
398
    node.
399

400
    Arguments:
401
      - target_node: the node we wish to configure for
402
      - nodes_ip: a mapping of node name to ip
403

404
    The target_node must exist in in nodes_ip, and must be one of the
405
    nodes in the logical ID for each of the DRBD devices encountered
406
    in the disk tree.
407

408
    """
409
    if self.children:
410
      for child in self.children:
411
        child.SetPhysicalID(target_node, nodes_ip)
412

    
413
    if self.logical_id is None and self.physical_id is not None:
414
      return
415
    if self.dev_type in constants.LDS_DRBD:
416
      pnode, snode, port, pminor, sminor, secret = self.logical_id
417
      if target_node not in (pnode, snode):
418
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
419
                                        target_node)
420
      pnode_ip = nodes_ip.get(pnode, None)
421
      snode_ip = nodes_ip.get(snode, None)
422
      if pnode_ip is None or snode_ip is None:
423
        raise errors.ConfigurationError("Can't find primary or secondary node"
424
                                        " for %s" % str(self))
425
      p_data = (pnode_ip, port)
426
      s_data = (snode_ip, port)
427
      if pnode == target_node:
428
        self.physical_id = p_data + s_data + (pminor, secret)
429
      else: # it must be secondary, we tested above
430
        self.physical_id = s_data + p_data + (sminor, secret)
431
    else:
432
      self.physical_id = self.logical_id
433
    return
434

    
435
  def ToDict(self):
436
    """Disk-specific conversion to standard python types.
437

438
    This replaces the children lists of objects with lists of
439
    standard python types.
440

441
    """
442
    bo = super(Disk, self).ToDict()
443

    
444
    for attr in ("children",):
445
      alist = bo.get(attr, None)
446
      if alist:
447
        bo[attr] = self._ContainerToDicts(alist)
448
    return bo
449

    
450
  @classmethod
451
  def FromDict(cls, val):
452
    """Custom function for Disks
453

454
    """
455
    obj = super(Disk, cls).FromDict(val)
456
    if obj.children:
457
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
458
    if obj.logical_id and isinstance(obj.logical_id, list):
459
      obj.logical_id = tuple(obj.logical_id)
460
    if obj.physical_id and isinstance(obj.physical_id, list):
461
      obj.physical_id = tuple(obj.physical_id)
462
    if obj.dev_type in constants.LDS_DRBD:
463
      # we need a tuple of length six here
464
      if len(obj.logical_id) < 6:
465
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
466
    return obj
467

    
468
  def __str__(self):
469
    """Custom str() formatter for disks.
470

471
    """
472
    if self.dev_type == constants.LD_LV:
473
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
474
    elif self.dev_type in constants.LDS_DRBD:
475
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
476
      val = "<DRBD8("
477
      if self.physical_id is None:
478
        phy = "unconfigured"
479
      else:
480
        phy = ("configured as %s:%s %s:%s" %
481
               (self.physical_id[0], self.physical_id[1],
482
                self.physical_id[2], self.physical_id[3]))
483

    
484
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
485
              (node_a, minor_a, node_b, minor_b, port, phy))
486
      if self.children and self.children.count(None) == 0:
487
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
488
      else:
489
        val += "no local storage"
490
    else:
491
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
492
             (self.dev_type, self.logical_id, self.physical_id, self.children))
493
    if self.iv_name is None:
494
      val += ", not visible"
495
    else:
496
      val += ", visible as /dev/%s" % self.iv_name
497
    val += ", size=%dm)>" % self.size
498
    return val
499

    
500
  def Verify(self):
501
    """Checks that this disk is correctly configured.
502

503
    """
504
    errors = []
505
    if self.mode not in constants.DISK_ACCESS_SET:
506
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
507
    return errors
508

    
509

    
510
class Instance(TaggableObject):
511
  """Config object representing an instance."""
512
  __slots__ = TaggableObject.__slots__ + [
513
    "name",
514
    "primary_node",
515
    "os",
516
    "hypervisor",
517
    "hvparams",
518
    "beparams",
519
    "admin_up",
520
    "nics",
521
    "disks",
522
    "disk_template",
523
    "network_port",
524
    "serial_no",
525
    ]
526

    
527
  def _ComputeSecondaryNodes(self):
528
    """Compute the list of secondary nodes.
529

530
    This is a simple wrapper over _ComputeAllNodes.
531

532
    """
533
    all_nodes = set(self._ComputeAllNodes())
534
    all_nodes.discard(self.primary_node)
535
    return tuple(all_nodes)
536

    
537
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
538
                             "List of secondary nodes")
539

    
540
  def _ComputeAllNodes(self):
541
    """Compute the list of all nodes.
542

543
    Since the data is already there (in the drbd disks), keeping it as
544
    a separate normal attribute is redundant and if not properly
545
    synchronised can cause problems. Thus it's better to compute it
546
    dynamically.
547

548
    """
549
    def _Helper(nodes, device):
550
      """Recursively computes nodes given a top device."""
551
      if device.dev_type in constants.LDS_DRBD:
552
        nodea, nodeb = device.logical_id[:2]
553
        nodes.add(nodea)
554
        nodes.add(nodeb)
555
      if device.children:
556
        for child in device.children:
557
          _Helper(nodes, child)
558

    
559
    all_nodes = set()
560
    all_nodes.add(self.primary_node)
561
    for device in self.disks:
562
      _Helper(all_nodes, device)
563
    return tuple(all_nodes)
564

    
565
  all_nodes = property(_ComputeAllNodes, None, None,
566
                       "List of all nodes of the instance")
567

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

571
    This function figures out what logical volumes should belong on
572
    which nodes, recursing through a device tree.
573

574
    @param lvmap: optional dictionary to receive the
575
        'node' : ['lv', ...] data.
576

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

580
    """
581
    if node == None:
582
      node = self.primary_node
583

    
584
    if lvmap is None:
585
      lvmap = { node : [] }
586
      ret = lvmap
587
    else:
588
      if not node in lvmap:
589
        lvmap[node] = []
590
      ret = None
591

    
592
    if not devs:
593
      devs = self.disks
594

    
595
    for dev in devs:
596
      if dev.dev_type == constants.LD_LV:
597
        lvmap[node].append(dev.logical_id[1])
598

    
599
      elif dev.dev_type in constants.LDS_DRBD:
600
        if dev.children:
601
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
602
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
603

    
604
      elif dev.children:
605
        self.MapLVsByNode(lvmap, dev.children, node)
606

    
607
    return ret
608

    
609
  def FindDisk(self, idx):
610
    """Find a disk given having a specified index.
611

612
    This is just a wrapper that does validation of the index.
613

614
    @type idx: int
615
    @param idx: the disk index
616
    @rtype: L{Disk}
617
    @return: the corresponding disk
618
    @raise errors.OpPrereqError: when the given index is not valid
619

620
    """
621
    try:
622
      idx = int(idx)
623
      return self.disks[idx]
624
    except ValueError, err:
625
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
626
    except IndexError:
627
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
628
                                 " 0 to %d" % (idx, len(self.disks)))
629

    
630
  def ToDict(self):
631
    """Instance-specific conversion to standard python types.
632

633
    This replaces the children lists of objects with lists of standard
634
    python types.
635

636
    """
637
    bo = super(Instance, self).ToDict()
638

    
639
    for attr in "nics", "disks":
640
      alist = bo.get(attr, None)
641
      if alist:
642
        nlist = self._ContainerToDicts(alist)
643
      else:
644
        nlist = []
645
      bo[attr] = nlist
646
    return bo
647

    
648
  @classmethod
649
  def FromDict(cls, val):
650
    """Custom function for instances.
651

652
    """
653
    obj = super(Instance, cls).FromDict(val)
654
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
655
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
656
    return obj
657

    
658

    
659
class OS(ConfigObject):
660
  """Config object representing an operating system."""
661
  __slots__ = [
662
    "name",
663
    "path",
664
    "status",
665
    "api_versions",
666
    "create_script",
667
    "export_script",
668
    "import_script",
669
    "rename_script",
670
    ]
671

    
672
  @classmethod
673
  def FromInvalidOS(cls, err):
674
    """Create an OS from an InvalidOS error.
675

676
    This routine knows how to convert an InvalidOS error to an OS
677
    object representing the broken OS with a meaningful error message.
678

679
    """
680
    if not isinstance(err, errors.InvalidOS):
681
      raise errors.ProgrammerError("Trying to initialize an OS from an"
682
                                   " invalid object of type %s" % type(err))
683

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

    
686
  def __nonzero__(self):
687
    return self.status == constants.OS_VALID_STATUS
688

    
689
  __bool__ = __nonzero__
690

    
691

    
692
class Node(TaggableObject):
693
  """Config object representing a node."""
694
  __slots__ = TaggableObject.__slots__ + [
695
    "name",
696
    "primary_ip",
697
    "secondary_ip",
698
    "serial_no",
699
    "master_candidate",
700
    "offline",
701
    "drained",
702
    ]
703

    
704

    
705
class Cluster(TaggableObject):
706
  """Config object representing the cluster."""
707
  __slots__ = TaggableObject.__slots__ + [
708
    "serial_no",
709
    "rsahostkeypub",
710
    "highest_used_port",
711
    "tcpudp_port_pool",
712
    "mac_prefix",
713
    "volume_group_name",
714
    "default_bridge",
715
    "default_hypervisor",
716
    "master_node",
717
    "master_ip",
718
    "master_netdev",
719
    "cluster_name",
720
    "file_storage_dir",
721
    "enabled_hypervisors",
722
    "hvparams",
723
    "beparams",
724
    "candidate_pool_size",
725
    ]
726

    
727
  def ToDict(self):
728
    """Custom function for cluster.
729

730
    """
731
    mydict = super(Cluster, self).ToDict()
732
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
733
    return mydict
734

    
735
  @classmethod
736
  def FromDict(cls, val):
737
    """Custom function for cluster.
738

739
    """
740
    obj = super(Cluster, cls).FromDict(val)
741
    if not isinstance(obj.tcpudp_port_pool, set):
742
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
743
    return obj
744

    
745
  @staticmethod
746
  def FillDict(defaults_dict, custom_dict):
747
    """Basic function to apply settings on top a default dict.
748

749
    @type defaults_dict: dict
750
    @param defaults_dict: dictionary holding the default values
751
    @type custom_dict: dict
752
    @param custom_dict: dictionary holding customized value
753
    @rtype: dict
754
    @return: dict with the 'full' values
755

756
    """
757
    ret_dict = copy.deepcopy(defaults_dict)
758
    ret_dict.update(custom_dict)
759
    return ret_dict
760

    
761
  def FillHV(self, instance):
762
    """Fill an instance's hvparams dict.
763

764
    @type instance: object
765
    @param instance: the instance parameter to fill
766
    @rtype: dict
767
    @return: a copy of the instance's hvparams with missing keys filled from
768
        the cluster defaults
769

770
    """
771
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
772
                         instance.hvparams)
773

    
774
  def FillBE(self, instance):
775
    """Fill an instance's beparams dict.
776

777
    @type instance: object
778
    @param instance: the instance parameter to fill
779
    @rtype: dict
780
    @return: a copy of the instance's beparams with missing keys filled from
781
        the cluster defaults
782

783
    """
784
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
785
                         instance.beparams)
786

    
787

    
788
class SerializableConfigParser(ConfigParser.SafeConfigParser):
789
  """Simple wrapper over ConfigParse that allows serialization.
790

791
  This class is basically ConfigParser.SafeConfigParser with two
792
  additional methods that allow it to serialize/unserialize to/from a
793
  buffer.
794

795
  """
796
  def Dumps(self):
797
    """Dump this instance and return the string representation."""
798
    buf = StringIO()
799
    self.write(buf)
800
    return buf.getvalue()
801

    
802
  @staticmethod
803
  def Loads(data):
804
    """Load data from a string."""
805
    buf = StringIO(data)
806
    cfp = SerializableConfigParser()
807
    cfp.readfp(buf)
808
    return cfp