Move the hooks file mask into constants.py
[ganeti-local] / lib / objects.py
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   def UpgradeConfig(self):
169     """Fill defaults for missing configuration values.
170
171     This method will be called at configuration load time, and its
172     implementation will be object dependent.
173
174     """
175     pass
176
177
178 class TaggableObject(ConfigObject):
179   """An generic class supporting tags.
180
181   """
182   __slots__ = ConfigObject.__slots__ + ["tags"]
183
184   @staticmethod
185   def ValidateTag(tag):
186     """Check if a tag is valid.
187
188     If the tag is invalid, an errors.TagError will be raised. The
189     function has no return value.
190
191     """
192     if not isinstance(tag, basestring):
193       raise errors.TagError("Invalid tag type (not a string)")
194     if len(tag) > constants.MAX_TAG_LEN:
195       raise errors.TagError("Tag too long (>%d characters)" %
196                             constants.MAX_TAG_LEN)
197     if not tag:
198       raise errors.TagError("Tags cannot be empty")
199     if not re.match("^[\w.+*/:-]+$", tag):
200       raise errors.TagError("Tag contains invalid characters")
201
202   def GetTags(self):
203     """Return the tags list.
204
205     """
206     tags = getattr(self, "tags", None)
207     if tags is None:
208       tags = self.tags = set()
209     return tags
210
211   def AddTag(self, tag):
212     """Add a new tag.
213
214     """
215     self.ValidateTag(tag)
216     tags = self.GetTags()
217     if len(tags) >= constants.MAX_TAGS_PER_OBJ:
218       raise errors.TagError("Too many tags")
219     self.GetTags().add(tag)
220
221   def RemoveTag(self, tag):
222     """Remove a tag.
223
224     """
225     self.ValidateTag(tag)
226     tags = self.GetTags()
227     try:
228       tags.remove(tag)
229     except KeyError:
230       raise errors.TagError("Tag not found")
231
232   def ToDict(self):
233     """Taggable-object-specific conversion to standard python types.
234
235     This replaces the tags set with a list.
236
237     """
238     bo = super(TaggableObject, self).ToDict()
239
240     tags = bo.get("tags", None)
241     if isinstance(tags, set):
242       bo["tags"] = list(tags)
243     return bo
244
245   @classmethod
246   def FromDict(cls, val):
247     """Custom function for instances.
248
249     """
250     obj = super(TaggableObject, cls).FromDict(val)
251     if hasattr(obj, "tags") and isinstance(obj.tags, list):
252       obj.tags = set(obj.tags)
253     return obj
254
255
256 class ConfigData(ConfigObject):
257   """Top-level config object."""
258   __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"]
259
260   def ToDict(self):
261     """Custom function for top-level config data.
262
263     This just replaces the list of instances, nodes and the cluster
264     with standard python types.
265
266     """
267     mydict = super(ConfigData, self).ToDict()
268     mydict["cluster"] = mydict["cluster"].ToDict()
269     for key in "nodes", "instances":
270       mydict[key] = self._ContainerToDicts(mydict[key])
271
272     return mydict
273
274   @classmethod
275   def FromDict(cls, val):
276     """Custom function for top-level config data
277
278     """
279     obj = super(ConfigData, cls).FromDict(val)
280     obj.cluster = Cluster.FromDict(obj.cluster)
281     obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
282     obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
283     return obj
284
285   def UpgradeConfig(self):
286     """Fill defaults for missing configuration values.
287
288     """
289     self.cluster.UpgradeConfig()
290     for node in self.nodes.values():
291       node.UpgradeConfig()
292     for instance in self.instances.values():
293       instance.UpgradeConfig()
294
295
296 class NIC(ConfigObject):
297   """Config object representing a network card."""
298   __slots__ = ["mac", "ip", "bridge"]
299
300
301 class Disk(ConfigObject):
302   """Config object representing a block device."""
303   __slots__ = ["dev_type", "logical_id", "physical_id",
304                "children", "iv_name", "size", "mode"]
305
306   def CreateOnSecondary(self):
307     """Test if this device needs to be created on a secondary node."""
308     return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
309
310   def AssembleOnSecondary(self):
311     """Test if this device needs to be assembled on a secondary node."""
312     return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
313
314   def OpenOnSecondary(self):
315     """Test if this device needs to be opened on a secondary node."""
316     return self.dev_type in (constants.LD_LV,)
317
318   def StaticDevPath(self):
319     """Return the device path if this device type has a static one.
320
321     Some devices (LVM for example) live always at the same /dev/ path,
322     irrespective of their status. For such devices, we return this
323     path, for others we return None.
324
325     """
326     if self.dev_type == constants.LD_LV:
327       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
328     return None
329
330   def ChildrenNeeded(self):
331     """Compute the needed number of children for activation.
332
333     This method will return either -1 (all children) or a positive
334     number denoting the minimum number of children needed for
335     activation (only mirrored devices will usually return >=0).
336
337     Currently, only DRBD8 supports diskless activation (therefore we
338     return 0), for all other we keep the previous semantics and return
339     -1.
340
341     """
342     if self.dev_type == constants.LD_DRBD8:
343       return 0
344     return -1
345
346   def GetNodes(self, node):
347     """This function returns the nodes this device lives on.
348
349     Given the node on which the parent of the device lives on (or, in
350     case of a top-level device, the primary node of the devices'
351     instance), this function will return a list of nodes on which this
352     devices needs to (or can) be assembled.
353
354     """
355     if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
356       result = [node]
357     elif self.dev_type in constants.LDS_DRBD:
358       result = [self.logical_id[0], self.logical_id[1]]
359       if node not in result:
360         raise errors.ConfigurationError("DRBD device passed unknown node")
361     else:
362       raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
363     return result
364
365   def ComputeNodeTree(self, parent_node):
366     """Compute the node/disk tree for this disk and its children.
367
368     This method, given the node on which the parent disk lives, will
369     return the list of all (node, disk) pairs which describe the disk
370     tree in the most compact way. For example, a drbd/lvm stack
371     will be returned as (primary_node, drbd) and (secondary_node, drbd)
372     which represents all the top-level devices on the nodes.
373
374     """
375     my_nodes = self.GetNodes(parent_node)
376     result = [(node, self) for node in my_nodes]
377     if not self.children:
378       # leaf device
379       return result
380     for node in my_nodes:
381       for child in self.children:
382         child_result = child.ComputeNodeTree(node)
383         if len(child_result) == 1:
384           # child (and all its descendants) is simple, doesn't split
385           # over multiple hosts, so we don't need to describe it, our
386           # own entry for this node describes it completely
387           continue
388         else:
389           # check if child nodes differ from my nodes; note that
390           # subdisk can differ from the child itself, and be instead
391           # one of its descendants
392           for subnode, subdisk in child_result:
393             if subnode not in my_nodes:
394               result.append((subnode, subdisk))
395             # otherwise child is under our own node, so we ignore this
396             # entry (but probably the other results in the list will
397             # be different)
398     return result
399
400   def RecordGrow(self, amount):
401     """Update the size of this disk after growth.
402
403     This method recurses over the disks's children and updates their
404     size correspondigly. The method needs to be kept in sync with the
405     actual algorithms from bdev.
406
407     """
408     if self.dev_type == constants.LD_LV:
409       self.size += amount
410     elif self.dev_type == constants.LD_DRBD8:
411       if self.children:
412         self.children[0].RecordGrow(amount)
413       self.size += amount
414     else:
415       raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
416                                    " disk type %s" % self.dev_type)
417
418   def UnsetSize(self):
419     """Sets recursively the size to zero for the disk and its children.
420
421     """
422     if self.children:
423       for child in self.children:
424         child.UnsetSize()
425     self.size = 0
426
427   def SetPhysicalID(self, target_node, nodes_ip):
428     """Convert the logical ID to the physical ID.
429
430     This is used only for drbd, which needs ip/port configuration.
431
432     The routine descends down and updates its children also, because
433     this helps when the only the top device is passed to the remote
434     node.
435
436     Arguments:
437       - target_node: the node we wish to configure for
438       - nodes_ip: a mapping of node name to ip
439
440     The target_node must exist in in nodes_ip, and must be one of the
441     nodes in the logical ID for each of the DRBD devices encountered
442     in the disk tree.
443
444     """
445     if self.children:
446       for child in self.children:
447         child.SetPhysicalID(target_node, nodes_ip)
448
449     if self.logical_id is None and self.physical_id is not None:
450       return
451     if self.dev_type in constants.LDS_DRBD:
452       pnode, snode, port, pminor, sminor, secret = self.logical_id
453       if target_node not in (pnode, snode):
454         raise errors.ConfigurationError("DRBD device not knowing node %s" %
455                                         target_node)
456       pnode_ip = nodes_ip.get(pnode, None)
457       snode_ip = nodes_ip.get(snode, None)
458       if pnode_ip is None or snode_ip is None:
459         raise errors.ConfigurationError("Can't find primary or secondary node"
460                                         " for %s" % str(self))
461       p_data = (pnode_ip, port)
462       s_data = (snode_ip, port)
463       if pnode == target_node:
464         self.physical_id = p_data + s_data + (pminor, secret)
465       else: # it must be secondary, we tested above
466         self.physical_id = s_data + p_data + (sminor, secret)
467     else:
468       self.physical_id = self.logical_id
469     return
470
471   def ToDict(self):
472     """Disk-specific conversion to standard python types.
473
474     This replaces the children lists of objects with lists of
475     standard python types.
476
477     """
478     bo = super(Disk, self).ToDict()
479
480     for attr in ("children",):
481       alist = bo.get(attr, None)
482       if alist:
483         bo[attr] = self._ContainerToDicts(alist)
484     return bo
485
486   @classmethod
487   def FromDict(cls, val):
488     """Custom function for Disks
489
490     """
491     obj = super(Disk, cls).FromDict(val)
492     if obj.children:
493       obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
494     if obj.logical_id and isinstance(obj.logical_id, list):
495       obj.logical_id = tuple(obj.logical_id)
496     if obj.physical_id and isinstance(obj.physical_id, list):
497       obj.physical_id = tuple(obj.physical_id)
498     if obj.dev_type in constants.LDS_DRBD:
499       # we need a tuple of length six here
500       if len(obj.logical_id) < 6:
501         obj.logical_id += (None,) * (6 - len(obj.logical_id))
502     return obj
503
504   def __str__(self):
505     """Custom str() formatter for disks.
506
507     """
508     if self.dev_type == constants.LD_LV:
509       val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
510     elif self.dev_type in constants.LDS_DRBD:
511       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
512       val = "<DRBD8("
513       if self.physical_id is None:
514         phy = "unconfigured"
515       else:
516         phy = ("configured as %s:%s %s:%s" %
517                (self.physical_id[0], self.physical_id[1],
518                 self.physical_id[2], self.physical_id[3]))
519
520       val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
521               (node_a, minor_a, node_b, minor_b, port, phy))
522       if self.children and self.children.count(None) == 0:
523         val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
524       else:
525         val += "no local storage"
526     else:
527       val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
528              (self.dev_type, self.logical_id, self.physical_id, self.children))
529     if self.iv_name is None:
530       val += ", not visible"
531     else:
532       val += ", visible as /dev/%s" % self.iv_name
533     if isinstance(self.size, int):
534       val += ", size=%dm)>" % self.size
535     else:
536       val += ", size='%s')>" % (self.size,)
537     return val
538
539   def Verify(self):
540     """Checks that this disk is correctly configured.
541
542     """
543     all_errors = []
544     if self.mode not in constants.DISK_ACCESS_SET:
545       all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
546     return all_errors
547
548   def UpgradeConfig(self):
549     """Fill defaults for missing configuration values.
550
551     """
552     if self.children:
553       for child in self.children:
554         child.UpgradeConfig()
555     # add here config upgrade for this disk
556
557
558 class Instance(TaggableObject):
559   """Config object representing an instance."""
560   __slots__ = TaggableObject.__slots__ + [
561     "name",
562     "primary_node",
563     "os",
564     "hypervisor",
565     "hvparams",
566     "beparams",
567     "admin_up",
568     "nics",
569     "disks",
570     "disk_template",
571     "network_port",
572     "serial_no",
573     ]
574
575   def _ComputeSecondaryNodes(self):
576     """Compute the list of secondary nodes.
577
578     This is a simple wrapper over _ComputeAllNodes.
579
580     """
581     all_nodes = set(self._ComputeAllNodes())
582     all_nodes.discard(self.primary_node)
583     return tuple(all_nodes)
584
585   secondary_nodes = property(_ComputeSecondaryNodes, None, None,
586                              "List of secondary nodes")
587
588   def _ComputeAllNodes(self):
589     """Compute the list of all nodes.
590
591     Since the data is already there (in the drbd disks), keeping it as
592     a separate normal attribute is redundant and if not properly
593     synchronised can cause problems. Thus it's better to compute it
594     dynamically.
595
596     """
597     def _Helper(nodes, device):
598       """Recursively computes nodes given a top device."""
599       if device.dev_type in constants.LDS_DRBD:
600         nodea, nodeb = device.logical_id[:2]
601         nodes.add(nodea)
602         nodes.add(nodeb)
603       if device.children:
604         for child in device.children:
605           _Helper(nodes, child)
606
607     all_nodes = set()
608     all_nodes.add(self.primary_node)
609     for device in self.disks:
610       _Helper(all_nodes, device)
611     return tuple(all_nodes)
612
613   all_nodes = property(_ComputeAllNodes, None, None,
614                        "List of all nodes of the instance")
615
616   def MapLVsByNode(self, lvmap=None, devs=None, node=None):
617     """Provide a mapping of nodes to LVs this instance owns.
618
619     This function figures out what logical volumes should belong on
620     which nodes, recursing through a device tree.
621
622     @param lvmap: optional dictionary to receive the
623         'node' : ['lv', ...] data.
624
625     @return: None if lvmap arg is given, otherwise, a dictionary
626         of the form { 'nodename' : ['volume1', 'volume2', ...], ... }
627
628     """
629     if node == None:
630       node = self.primary_node
631
632     if lvmap is None:
633       lvmap = { node : [] }
634       ret = lvmap
635     else:
636       if not node in lvmap:
637         lvmap[node] = []
638       ret = None
639
640     if not devs:
641       devs = self.disks
642
643     for dev in devs:
644       if dev.dev_type == constants.LD_LV:
645         lvmap[node].append(dev.logical_id[1])
646
647       elif dev.dev_type in constants.LDS_DRBD:
648         if dev.children:
649           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
650           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
651
652       elif dev.children:
653         self.MapLVsByNode(lvmap, dev.children, node)
654
655     return ret
656
657   def FindDisk(self, idx):
658     """Find a disk given having a specified index.
659
660     This is just a wrapper that does validation of the index.
661
662     @type idx: int
663     @param idx: the disk index
664     @rtype: L{Disk}
665     @return: the corresponding disk
666     @raise errors.OpPrereqError: when the given index is not valid
667
668     """
669     try:
670       idx = int(idx)
671       return self.disks[idx]
672     except ValueError, err:
673       raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err))
674     except IndexError:
675       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
676                                  " 0 to %d" % (idx, len(self.disks)))
677
678   def ToDict(self):
679     """Instance-specific conversion to standard python types.
680
681     This replaces the children lists of objects with lists of standard
682     python types.
683
684     """
685     bo = super(Instance, self).ToDict()
686
687     for attr in "nics", "disks":
688       alist = bo.get(attr, None)
689       if alist:
690         nlist = self._ContainerToDicts(alist)
691       else:
692         nlist = []
693       bo[attr] = nlist
694     return bo
695
696   @classmethod
697   def FromDict(cls, val):
698     """Custom function for instances.
699
700     """
701     obj = super(Instance, cls).FromDict(val)
702     obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
703     obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
704     return obj
705
706   def UpgradeConfig(self):
707     """Fill defaults for missing configuration values.
708
709     """
710     for nic in self.nics:
711       nic.UpgradeConfig()
712     for disk in self.disks:
713       disk.UpgradeConfig()
714
715
716 class OS(ConfigObject):
717   """Config object representing an operating system."""
718   __slots__ = [
719     "name",
720     "path",
721     "status",
722     "api_versions",
723     "create_script",
724     "export_script",
725     "import_script",
726     "rename_script",
727     ]
728
729   @classmethod
730   def FromInvalidOS(cls, err):
731     """Create an OS from an InvalidOS error.
732
733     This routine knows how to convert an InvalidOS error to an OS
734     object representing the broken OS with a meaningful error message.
735
736     """
737     if not isinstance(err, errors.InvalidOS):
738       raise errors.ProgrammerError("Trying to initialize an OS from an"
739                                    " invalid object of type %s" % type(err))
740
741     return cls(name=err.args[0], path=err.args[1], status=err.args[2])
742
743   def __nonzero__(self):
744     return self.status == constants.OS_VALID_STATUS
745
746   __bool__ = __nonzero__
747
748
749 class Node(TaggableObject):
750   """Config object representing a node."""
751   __slots__ = TaggableObject.__slots__ + [
752     "name",
753     "primary_ip",
754     "secondary_ip",
755     "serial_no",
756     "master_candidate",
757     "offline",
758     "drained",
759     ]
760
761
762 class Cluster(TaggableObject):
763   """Config object representing the cluster."""
764   __slots__ = TaggableObject.__slots__ + [
765     "serial_no",
766     "rsahostkeypub",
767     "highest_used_port",
768     "tcpudp_port_pool",
769     "mac_prefix",
770     "volume_group_name",
771     "default_bridge",
772     "default_hypervisor",
773     "master_node",
774     "master_ip",
775     "master_netdev",
776     "cluster_name",
777     "file_storage_dir",
778     "enabled_hypervisors",
779     "hvparams",
780     "beparams",
781     "candidate_pool_size",
782     "modify_etc_hosts",
783     ]
784
785   def UpgradeConfig(self):
786     """Fill defaults for missing configuration values.
787
788     """
789     if self.hvparams is None:
790       self.hvparams = constants.HVC_DEFAULTS
791     else:
792       for hypervisor in self.hvparams:
793         self.hvparams[hypervisor] = self.FillDict(
794             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
795
796     if self.beparams is None:
797       self.beparams = {constants.BEGR_DEFAULT: constants.BEC_DEFAULTS}
798     else:
799       for begroup in self.beparams:
800         self.beparams[begroup] = self.FillDict(constants.BEC_DEFAULTS,
801                                                self.beparams[begroup])
802
803     if self.modify_etc_hosts is None:
804       self.modify_etc_hosts = True
805
806   def ToDict(self):
807     """Custom function for cluster.
808
809     """
810     mydict = super(Cluster, self).ToDict()
811     mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
812     return mydict
813
814   @classmethod
815   def FromDict(cls, val):
816     """Custom function for cluster.
817
818     """
819     obj = super(Cluster, cls).FromDict(val)
820     if not isinstance(obj.tcpudp_port_pool, set):
821       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
822     return obj
823
824   @staticmethod
825   def FillDict(defaults_dict, custom_dict):
826     """Basic function to apply settings on top a default dict.
827
828     @type defaults_dict: dict
829     @param defaults_dict: dictionary holding the default values
830     @type custom_dict: dict
831     @param custom_dict: dictionary holding customized value
832     @rtype: dict
833     @return: dict with the 'full' values
834
835     """
836     ret_dict = copy.deepcopy(defaults_dict)
837     ret_dict.update(custom_dict)
838     return ret_dict
839
840   def FillHV(self, instance):
841     """Fill an instance's hvparams dict.
842
843     @type instance: L{objects.Instance}
844     @param instance: the instance parameter to fill
845     @rtype: dict
846     @return: a copy of the instance's hvparams with missing keys filled from
847         the cluster defaults
848
849     """
850     return self.FillDict(self.hvparams.get(instance.hypervisor, {}),
851                          instance.hvparams)
852
853   def FillBE(self, instance):
854     """Fill an instance's beparams dict.
855
856     @type instance: L{objects.Instance}
857     @param instance: the instance parameter to fill
858     @rtype: dict
859     @return: a copy of the instance's beparams with missing keys filled from
860         the cluster defaults
861
862     """
863     return self.FillDict(self.beparams.get(constants.BEGR_DEFAULT, {}),
864                          instance.beparams)
865
866
867 class SerializableConfigParser(ConfigParser.SafeConfigParser):
868   """Simple wrapper over ConfigParse that allows serialization.
869
870   This class is basically ConfigParser.SafeConfigParser with two
871   additional methods that allow it to serialize/unserialize to/from a
872   buffer.
873
874   """
875   def Dumps(self):
876     """Dump this instance and return the string representation."""
877     buf = StringIO()
878     self.write(buf)
879     return buf.getvalue()
880
881   @staticmethod
882   def Loads(data):
883     """Load data from a string."""
884     buf = StringIO(data)
885     cfp = SerializableConfigParser()
886     cfp.readfp(buf)
887     return cfp