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