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