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