Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ e8d563f3

History | View | Annotate | Download (24.3 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 SetPhysicalID(self, target_node, nodes_ip):
400
    """Convert the logical ID to the physical ID.
401

402
    This is used only for drbd, which needs ip/port configuration.
403

404
    The routine descends down and updates its children also, because
405
    this helps when the only the top device is passed to the remote
406
    node.
407

408
    Arguments:
409
      - target_node: the node we wish to configure for
410
      - nodes_ip: a mapping of node name to ip
411

412
    The target_node must exist in in nodes_ip, and must be one of the
413
    nodes in the logical ID for each of the DRBD devices encountered
414
    in the disk tree.
415

416
    """
417
    if self.children:
418
      for child in self.children:
419
        child.SetPhysicalID(target_node, nodes_ip)
420

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

    
443
  def ToDict(self):
444
    """Disk-specific conversion to standard python types.
445

446
    This replaces the children lists of objects with lists of
447
    standard python types.
448

449
    """
450
    bo = super(Disk, self).ToDict()
451

    
452
    for attr in ("children",):
453
      alist = bo.get(attr, None)
454
      if alist:
455
        bo[attr] = self._ContainerToDicts(alist)
456
    return bo
457

    
458
  @classmethod
459
  def FromDict(cls, val):
460
    """Custom function for Disks
461

462
    """
463
    obj = super(Disk, cls).FromDict(val)
464
    if obj.children:
465
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
466
    if obj.logical_id and isinstance(obj.logical_id, list):
467
      obj.logical_id = tuple(obj.logical_id)
468
    if obj.physical_id and isinstance(obj.physical_id, list):
469
      obj.physical_id = tuple(obj.physical_id)
470
    if obj.dev_type in constants.LDS_DRBD:
471
      # we need a tuple of length six here
472
      if len(obj.logical_id) < 6:
473
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
474
    return obj
475

    
476
  def __str__(self):
477
    """Custom str() formatter for disks.
478

479
    """
480
    if self.dev_type == constants.LD_LV:
481
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
482
    elif self.dev_type in constants.LDS_DRBD:
483
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
484
      val = "<DRBD8("
485
      if self.physical_id is None:
486
        phy = "unconfigured"
487
      else:
488
        phy = ("configured as %s:%s %s:%s" %
489
               (self.physical_id[0], self.physical_id[1],
490
                self.physical_id[2], self.physical_id[3]))
491

    
492
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
493
              (node_a, minor_a, node_b, minor_b, port, phy))
494
      if self.children and self.children.count(None) == 0:
495
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
496
      else:
497
        val += "no local storage"
498
    else:
499
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
500
             (self.dev_type, self.logical_id, self.physical_id, self.children))
501
    if self.iv_name is None:
502
      val += ", not visible"
503
    else:
504
      val += ", visible as /dev/%s" % self.iv_name
505
    if isinstance(self.size, int):
506
      val += ", size=%dm)>" % self.size
507
    else:
508
      val += ", size='%s')>" % (self.size,)
509
    return val
510

    
511
  def Verify(self):
512
    """Checks that this disk is correctly configured.
513

514
    """
515
    errors = []
516
    if self.mode not in constants.DISK_ACCESS_SET:
517
      errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
518
    return errors
519

    
520

    
521
class Instance(TaggableObject):
522
  """Config object representing an instance."""
523
  __slots__ = TaggableObject.__slots__ + [
524
    "name",
525
    "primary_node",
526
    "os",
527
    "hypervisor",
528
    "hvparams",
529
    "beparams",
530
    "admin_up",
531
    "nics",
532
    "disks",
533
    "disk_template",
534
    "network_port",
535
    "serial_no",
536
    ]
537

    
538
  def _ComputeSecondaryNodes(self):
539
    """Compute the list of secondary nodes.
540

541
    This is a simple wrapper over _ComputeAllNodes.
542

543
    """
544
    all_nodes = set(self._ComputeAllNodes())
545
    all_nodes.discard(self.primary_node)
546
    return tuple(all_nodes)
547

    
548
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
549
                             "List of secondary nodes")
550

    
551
  def _ComputeAllNodes(self):
552
    """Compute the list of all nodes.
553

554
    Since the data is already there (in the drbd disks), keeping it as
555
    a separate normal attribute is redundant and if not properly
556
    synchronised can cause problems. Thus it's better to compute it
557
    dynamically.
558

559
    """
560
    def _Helper(nodes, device):
561
      """Recursively computes nodes given a top device."""
562
      if device.dev_type in constants.LDS_DRBD:
563
        nodea, nodeb = device.logical_id[:2]
564
        nodes.add(nodea)
565
        nodes.add(nodeb)
566
      if device.children:
567
        for child in device.children:
568
          _Helper(nodes, child)
569

    
570
    all_nodes = set()
571
    all_nodes.add(self.primary_node)
572
    for device in self.disks:
573
      _Helper(all_nodes, device)
574
    return tuple(all_nodes)
575

    
576
  all_nodes = property(_ComputeAllNodes, None, None,
577
                       "List of all nodes of the instance")
578

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

582
    This function figures out what logical volumes should belong on
583
    which nodes, recursing through a device tree.
584

585
    @param lvmap: optional dictionary to receive the
586
        'node' : ['lv', ...] data.
587

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

591
    """
592
    if node == None:
593
      node = self.primary_node
594

    
595
    if lvmap is None:
596
      lvmap = { node : [] }
597
      ret = lvmap
598
    else:
599
      if not node in lvmap:
600
        lvmap[node] = []
601
      ret = None
602

    
603
    if not devs:
604
      devs = self.disks
605

    
606
    for dev in devs:
607
      if dev.dev_type == constants.LD_LV:
608
        lvmap[node].append(dev.logical_id[1])
609

    
610
      elif dev.dev_type in constants.LDS_DRBD:
611
        if dev.children:
612
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
613
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
614

    
615
      elif dev.children:
616
        self.MapLVsByNode(lvmap, dev.children, node)
617

    
618
    return ret
619

    
620
  def FindDisk(self, idx):
621
    """Find a disk given having a specified index.
622

623
    This is just a wrapper that does validation of the index.
624

625
    @type idx: int
626
    @param idx: the disk index
627
    @rtype: L{Disk}
628
    @return: the corresponding disk
629
    @raise errors.OpPrereqError: when the given index is not valid
630

631
    """
632
    try:
633
      idx = int(idx)
634
      return self.disks[idx]
635
    except ValueError, err:
636
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
637
    except IndexError:
638
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
639
                                 " 0 to %d" % (idx, len(self.disks)))
640

    
641
  def ToDict(self):
642
    """Instance-specific conversion to standard python types.
643

644
    This replaces the children lists of objects with lists of standard
645
    python types.
646

647
    """
648
    bo = super(Instance, self).ToDict()
649

    
650
    for attr in "nics", "disks":
651
      alist = bo.get(attr, None)
652
      if alist:
653
        nlist = self._ContainerToDicts(alist)
654
      else:
655
        nlist = []
656
      bo[attr] = nlist
657
    return bo
658

    
659
  @classmethod
660
  def FromDict(cls, val):
661
    """Custom function for instances.
662

663
    """
664
    obj = super(Instance, cls).FromDict(val)
665
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
666
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
667
    return obj
668

    
669

    
670
class OS(ConfigObject):
671
  """Config object representing an operating system."""
672
  __slots__ = [
673
    "name",
674
    "path",
675
    "status",
676
    "api_versions",
677
    "create_script",
678
    "export_script",
679
    "import_script",
680
    "rename_script",
681
    ]
682

    
683
  @classmethod
684
  def FromInvalidOS(cls, err):
685
    """Create an OS from an InvalidOS error.
686

687
    This routine knows how to convert an InvalidOS error to an OS
688
    object representing the broken OS with a meaningful error message.
689

690
    """
691
    if not isinstance(err, errors.InvalidOS):
692
      raise errors.ProgrammerError("Trying to initialize an OS from an"
693
                                   " invalid object of type %s" % type(err))
694

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

    
697
  def __nonzero__(self):
698
    return self.status == constants.OS_VALID_STATUS
699

    
700
  __bool__ = __nonzero__
701

    
702

    
703
class Node(TaggableObject):
704
  """Config object representing a node."""
705
  __slots__ = TaggableObject.__slots__ + [
706
    "name",
707
    "primary_ip",
708
    "secondary_ip",
709
    "serial_no",
710
    "master_candidate",
711
    "offline",
712
    "drained",
713
    ]
714

    
715

    
716
class Cluster(TaggableObject):
717
  """Config object representing the cluster."""
718
  __slots__ = TaggableObject.__slots__ + [
719
    "serial_no",
720
    "rsahostkeypub",
721
    "highest_used_port",
722
    "tcpudp_port_pool",
723
    "mac_prefix",
724
    "volume_group_name",
725
    "default_bridge",
726
    "default_hypervisor",
727
    "master_node",
728
    "master_ip",
729
    "master_netdev",
730
    "cluster_name",
731
    "file_storage_dir",
732
    "enabled_hypervisors",
733
    "hvparams",
734
    "beparams",
735
    "candidate_pool_size",
736
    ]
737

    
738
  def ToDict(self):
739
    """Custom function for cluster.
740

741
    """
742
    mydict = super(Cluster, self).ToDict()
743
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
744
    return mydict
745

    
746
  @classmethod
747
  def FromDict(cls, val):
748
    """Custom function for cluster.
749

750
    """
751
    obj = super(Cluster, cls).FromDict(val)
752
    if not isinstance(obj.tcpudp_port_pool, set):
753
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
754
    return obj
755

    
756
  @staticmethod
757
  def FillDict(defaults_dict, custom_dict):
758
    """Basic function to apply settings on top a default dict.
759

760
    @type defaults_dict: dict
761
    @param defaults_dict: dictionary holding the default values
762
    @type custom_dict: dict
763
    @param custom_dict: dictionary holding customized value
764
    @rtype: dict
765
    @return: dict with the 'full' values
766

767
    """
768
    ret_dict = copy.deepcopy(defaults_dict)
769
    ret_dict.update(custom_dict)
770
    return ret_dict
771

    
772
  def FillHV(self, instance):
773
    """Fill an instance's hvparams dict.
774

775
    @type instance: L{objects.Instance}
776
    @param instance: the instance parameter to fill
777
    @rtype: dict
778
    @return: a copy of the instance's hvparams with missing keys filled from
779
        the cluster defaults
780

781
    """
782
    return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
783
                         instance.hvparams)
784

    
785
  def FillBE(self, instance):
786
    """Fill an instance's beparams dict.
787

788
    @type instance: L{objects.Instance}
789
    @param instance: the instance parameter to fill
790
    @rtype: dict
791
    @return: a copy of the instance's beparams with missing keys filled from
792
        the cluster defaults
793

794
    """
795
    return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
796
                         instance.beparams)
797

    
798

    
799
class SerializableConfigParser(ConfigParser.SafeConfigParser):
800
  """Simple wrapper over ConfigParse that allows serialization.
801

802
  This class is basically ConfigParser.SafeConfigParser with two
803
  additional methods that allow it to serialize/unserialize to/from a
804
  buffer.
805

806
  """
807
  def Dumps(self):
808
    """Dump this instance and return the string representation."""
809
    buf = StringIO()
810
    self.write(buf)
811
    return buf.getvalue()
812

    
813
  @staticmethod
814
  def Loads(data):
815
    """Load data from a string."""
816
    buf = StringIO(data)
817
    cfp = SerializableConfigParser()
818
    cfp.readfp(buf)
819
    return cfp