locking: Allow checking if lock is owned in certain mode
[ganeti-local] / lib / objects.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 # pylint: disable=E0203,W0201,R0902
30
31 # E0203: Access to member %r before its definition, since we use
32 # objects.py which doesn't explicitely initialise its members
33
34 # W0201: Attribute '%s' defined outside __init__
35
36 # R0902: Allow instances of these objects to have more than 20 attributes
37
38 import ConfigParser
39 import re
40 import copy
41 import time
42 from cStringIO import StringIO
43
44 from ganeti import errors
45 from ganeti import constants
46 from ganeti import netutils
47
48 from socket import AF_INET
49
50
51 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
52            "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
53
54 _TIMESTAMPS = ["ctime", "mtime"]
55 _UUID = ["uuid"]
56
57
58 def FillDict(defaults_dict, custom_dict, skip_keys=None):
59   """Basic function to apply settings on top a default dict.
60
61   @type defaults_dict: dict
62   @param defaults_dict: dictionary holding the default values
63   @type custom_dict: dict
64   @param custom_dict: dictionary holding customized value
65   @type skip_keys: list
66   @param skip_keys: which keys not to fill
67   @rtype: dict
68   @return: dict with the 'full' values
69
70   """
71   ret_dict = copy.deepcopy(defaults_dict)
72   ret_dict.update(custom_dict)
73   if skip_keys:
74     for k in skip_keys:
75       try:
76         del ret_dict[k]
77       except KeyError:
78         pass
79   return ret_dict
80
81
82 def UpgradeGroupedParams(target, defaults):
83   """Update all groups for the target parameter.
84
85   @type target: dict of dicts
86   @param target: {group: {parameter: value}}
87   @type defaults: dict
88   @param defaults: default parameter values
89
90   """
91   if target is None:
92     target = {constants.PP_DEFAULT: defaults}
93   else:
94     for group in target:
95       target[group] = FillDict(defaults, target[group])
96   return target
97
98
99 def UpgradeBeParams(target):
100   """Update the be parameters dict to the new format.
101
102   @type target: dict
103   @param target: "be" parameters dict
104
105   """
106   if constants.BE_MEMORY in target:
107     memory = target[constants.BE_MEMORY]
108     target[constants.BE_MAXMEM] = memory
109     target[constants.BE_MINMEM] = memory
110     del target[constants.BE_MEMORY]
111
112
113 class ConfigObject(object):
114   """A generic config object.
115
116   It has the following properties:
117
118     - provides somewhat safe recursive unpickling and pickling for its classes
119     - unset attributes which are defined in slots are always returned
120       as None instead of raising an error
121
122   Classes derived from this must always declare __slots__ (we use many
123   config objects and the memory reduction is useful)
124
125   """
126   __slots__ = []
127
128   def __init__(self, **kwargs):
129     for k, v in kwargs.iteritems():
130       setattr(self, k, v)
131
132   def __getattr__(self, name):
133     if name not in self._all_slots():
134       raise AttributeError("Invalid object attribute %s.%s" %
135                            (type(self).__name__, name))
136     return None
137
138   def __setstate__(self, state):
139     slots = self._all_slots()
140     for name in state:
141       if name in slots:
142         setattr(self, name, state[name])
143
144   @classmethod
145   def _all_slots(cls):
146     """Compute the list of all declared slots for a class.
147
148     """
149     slots = []
150     for parent in cls.__mro__:
151       slots.extend(getattr(parent, "__slots__", []))
152     return slots
153
154   def ToDict(self):
155     """Convert to a dict holding only standard python types.
156
157     The generic routine just dumps all of this object's attributes in
158     a dict. It does not work if the class has children who are
159     ConfigObjects themselves (e.g. the nics list in an Instance), in
160     which case the object should subclass the function in order to
161     make sure all objects returned are only standard python types.
162
163     """
164     result = {}
165     for name in self._all_slots():
166       value = getattr(self, name, None)
167       if value is not None:
168         result[name] = value
169     return result
170
171   __getstate__ = ToDict
172
173   @classmethod
174   def FromDict(cls, val):
175     """Create an object from a dictionary.
176
177     This generic routine takes a dict, instantiates a new instance of
178     the given class, and sets attributes based on the dict content.
179
180     As for `ToDict`, this does not work if the class has children
181     who are ConfigObjects themselves (e.g. the nics list in an
182     Instance), in which case the object should subclass the function
183     and alter the objects.
184
185     """
186     if not isinstance(val, dict):
187       raise errors.ConfigurationError("Invalid object passed to FromDict:"
188                                       " expected dict, got %s" % type(val))
189     val_str = dict([(str(k), v) for k, v in val.iteritems()])
190     obj = cls(**val_str) # pylint: disable=W0142
191     return obj
192
193   @staticmethod
194   def _ContainerToDicts(container):
195     """Convert the elements of a container to standard python types.
196
197     This method converts a container with elements derived from
198     ConfigData to standard python types. If the container is a dict,
199     we don't touch the keys, only the values.
200
201     """
202     if isinstance(container, dict):
203       ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
204     elif isinstance(container, (list, tuple, set, frozenset)):
205       ret = [elem.ToDict() for elem in container]
206     else:
207       raise TypeError("Invalid type %s passed to _ContainerToDicts" %
208                       type(container))
209     return ret
210
211   @staticmethod
212   def _ContainerFromDicts(source, c_type, e_type):
213     """Convert a container from standard python types.
214
215     This method converts a container with standard python types to
216     ConfigData objects. If the container is a dict, we don't touch the
217     keys, only the values.
218
219     """
220     if not isinstance(c_type, type):
221       raise TypeError("Container type %s passed to _ContainerFromDicts is"
222                       " not a type" % type(c_type))
223     if source is None:
224       source = c_type()
225     if c_type is dict:
226       ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
227     elif c_type in (list, tuple, set, frozenset):
228       ret = c_type([e_type.FromDict(elem) for elem in source])
229     else:
230       raise TypeError("Invalid container type %s passed to"
231                       " _ContainerFromDicts" % c_type)
232     return ret
233
234   def Copy(self):
235     """Makes a deep copy of the current object and its children.
236
237     """
238     dict_form = self.ToDict()
239     clone_obj = self.__class__.FromDict(dict_form)
240     return clone_obj
241
242   def __repr__(self):
243     """Implement __repr__ for ConfigObjects."""
244     return repr(self.ToDict())
245
246   def UpgradeConfig(self):
247     """Fill defaults for missing configuration values.
248
249     This method will be called at configuration load time, and its
250     implementation will be object dependent.
251
252     """
253     pass
254
255
256 class TaggableObject(ConfigObject):
257   """An generic class supporting tags.
258
259   """
260   __slots__ = ["tags"]
261   VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
262
263   @classmethod
264   def ValidateTag(cls, tag):
265     """Check if a tag is valid.
266
267     If the tag is invalid, an errors.TagError will be raised. The
268     function has no return value.
269
270     """
271     if not isinstance(tag, basestring):
272       raise errors.TagError("Invalid tag type (not a string)")
273     if len(tag) > constants.MAX_TAG_LEN:
274       raise errors.TagError("Tag too long (>%d characters)" %
275                             constants.MAX_TAG_LEN)
276     if not tag:
277       raise errors.TagError("Tags cannot be empty")
278     if not cls.VALID_TAG_RE.match(tag):
279       raise errors.TagError("Tag contains invalid characters")
280
281   def GetTags(self):
282     """Return the tags list.
283
284     """
285     tags = getattr(self, "tags", None)
286     if tags is None:
287       tags = self.tags = set()
288     return tags
289
290   def AddTag(self, tag):
291     """Add a new tag.
292
293     """
294     self.ValidateTag(tag)
295     tags = self.GetTags()
296     if len(tags) >= constants.MAX_TAGS_PER_OBJ:
297       raise errors.TagError("Too many tags")
298     self.GetTags().add(tag)
299
300   def RemoveTag(self, tag):
301     """Remove a tag.
302
303     """
304     self.ValidateTag(tag)
305     tags = self.GetTags()
306     try:
307       tags.remove(tag)
308     except KeyError:
309       raise errors.TagError("Tag not found")
310
311   def ToDict(self):
312     """Taggable-object-specific conversion to standard python types.
313
314     This replaces the tags set with a list.
315
316     """
317     bo = super(TaggableObject, self).ToDict()
318
319     tags = bo.get("tags", None)
320     if isinstance(tags, set):
321       bo["tags"] = list(tags)
322     return bo
323
324   @classmethod
325   def FromDict(cls, val):
326     """Custom function for instances.
327
328     """
329     obj = super(TaggableObject, cls).FromDict(val)
330     if hasattr(obj, "tags") and isinstance(obj.tags, list):
331       obj.tags = set(obj.tags)
332     return obj
333
334
335 class MasterNetworkParameters(ConfigObject):
336   """Network configuration parameters for the master
337
338   @ivar name: master name
339   @ivar ip: master IP
340   @ivar netmask: master netmask
341   @ivar netdev: master network device
342   @ivar ip_family: master IP family
343
344   """
345   __slots__ = [
346     "name",
347     "ip",
348     "netmask",
349     "netdev",
350     "ip_family"
351     ]
352
353
354 class ConfigData(ConfigObject):
355   """Top-level config object."""
356   __slots__ = [
357     "version",
358     "cluster",
359     "nodes",
360     "nodegroups",
361     "instances",
362     "serial_no",
363     ] + _TIMESTAMPS
364
365   def ToDict(self):
366     """Custom function for top-level config data.
367
368     This just replaces the list of instances, nodes and the cluster
369     with standard python types.
370
371     """
372     mydict = super(ConfigData, self).ToDict()
373     mydict["cluster"] = mydict["cluster"].ToDict()
374     for key in "nodes", "instances", "nodegroups":
375       mydict[key] = self._ContainerToDicts(mydict[key])
376
377     return mydict
378
379   @classmethod
380   def FromDict(cls, val):
381     """Custom function for top-level config data
382
383     """
384     obj = super(ConfigData, cls).FromDict(val)
385     obj.cluster = Cluster.FromDict(obj.cluster)
386     obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
387     obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
388     obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
389     return obj
390
391   def HasAnyDiskOfType(self, dev_type):
392     """Check if in there is at disk of the given type in the configuration.
393
394     @type dev_type: L{constants.LDS_BLOCK}
395     @param dev_type: the type to look for
396     @rtype: boolean
397     @return: boolean indicating if a disk of the given type was found or not
398
399     """
400     for instance in self.instances.values():
401       for disk in instance.disks:
402         if disk.IsBasedOnDiskType(dev_type):
403           return True
404     return False
405
406   def UpgradeConfig(self):
407     """Fill defaults for missing configuration values.
408
409     """
410     self.cluster.UpgradeConfig()
411     for node in self.nodes.values():
412       node.UpgradeConfig()
413     for instance in self.instances.values():
414       instance.UpgradeConfig()
415     if self.nodegroups is None:
416       self.nodegroups = {}
417     for nodegroup in self.nodegroups.values():
418       nodegroup.UpgradeConfig()
419     if self.cluster.drbd_usermode_helper is None:
420       # To decide if we set an helper let's check if at least one instance has
421       # a DRBD disk. This does not cover all the possible scenarios but it
422       # gives a good approximation.
423       if self.HasAnyDiskOfType(constants.LD_DRBD8):
424         self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
425
426
427 class NIC(ConfigObject):
428   """Config object representing a network card."""
429   __slots__ = ["mac", "ip", "nicparams"]
430
431   @classmethod
432   def CheckParameterSyntax(cls, nicparams):
433     """Check the given parameters for validity.
434
435     @type nicparams:  dict
436     @param nicparams: dictionary with parameter names/value
437     @raise errors.ConfigurationError: when a parameter is not valid
438
439     """
440     if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
441         nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
442       err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
443       raise errors.ConfigurationError(err)
444
445     if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
446         not nicparams[constants.NIC_LINK]):
447       err = "Missing bridged nic link"
448       raise errors.ConfigurationError(err)
449
450
451 class Disk(ConfigObject):
452   """Config object representing a block device."""
453   __slots__ = ["dev_type", "logical_id", "physical_id",
454                "children", "iv_name", "size", "mode"]
455
456   def CreateOnSecondary(self):
457     """Test if this device needs to be created on a secondary node."""
458     return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
459
460   def AssembleOnSecondary(self):
461     """Test if this device needs to be assembled on a secondary node."""
462     return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
463
464   def OpenOnSecondary(self):
465     """Test if this device needs to be opened on a secondary node."""
466     return self.dev_type in (constants.LD_LV,)
467
468   def StaticDevPath(self):
469     """Return the device path if this device type has a static one.
470
471     Some devices (LVM for example) live always at the same /dev/ path,
472     irrespective of their status. For such devices, we return this
473     path, for others we return None.
474
475     @warning: The path returned is not a normalized pathname; callers
476         should check that it is a valid path.
477
478     """
479     if self.dev_type == constants.LD_LV:
480       return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
481     elif self.dev_type == constants.LD_BLOCKDEV:
482       return self.logical_id[1]
483     return None
484
485   def ChildrenNeeded(self):
486     """Compute the needed number of children for activation.
487
488     This method will return either -1 (all children) or a positive
489     number denoting the minimum number of children needed for
490     activation (only mirrored devices will usually return >=0).
491
492     Currently, only DRBD8 supports diskless activation (therefore we
493     return 0), for all other we keep the previous semantics and return
494     -1.
495
496     """
497     if self.dev_type == constants.LD_DRBD8:
498       return 0
499     return -1
500
501   def IsBasedOnDiskType(self, dev_type):
502     """Check if the disk or its children are based on the given type.
503
504     @type dev_type: L{constants.LDS_BLOCK}
505     @param dev_type: the type to look for
506     @rtype: boolean
507     @return: boolean indicating if a device of the given type was found or not
508
509     """
510     if self.children:
511       for child in self.children:
512         if child.IsBasedOnDiskType(dev_type):
513           return True
514     return self.dev_type == dev_type
515
516   def GetNodes(self, node):
517     """This function returns the nodes this device lives on.
518
519     Given the node on which the parent of the device lives on (or, in
520     case of a top-level device, the primary node of the devices'
521     instance), this function will return a list of nodes on which this
522     devices needs to (or can) be assembled.
523
524     """
525     if self.dev_type in [constants.LD_LV, constants.LD_FILE,
526                          constants.LD_BLOCKDEV]:
527       result = [node]
528     elif self.dev_type in constants.LDS_DRBD:
529       result = [self.logical_id[0], self.logical_id[1]]
530       if node not in result:
531         raise errors.ConfigurationError("DRBD device passed unknown node")
532     else:
533       raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
534     return result
535
536   def ComputeNodeTree(self, parent_node):
537     """Compute the node/disk tree for this disk and its children.
538
539     This method, given the node on which the parent disk lives, will
540     return the list of all (node, disk) pairs which describe the disk
541     tree in the most compact way. For example, a drbd/lvm stack
542     will be returned as (primary_node, drbd) and (secondary_node, drbd)
543     which represents all the top-level devices on the nodes.
544
545     """
546     my_nodes = self.GetNodes(parent_node)
547     result = [(node, self) for node in my_nodes]
548     if not self.children:
549       # leaf device
550       return result
551     for node in my_nodes:
552       for child in self.children:
553         child_result = child.ComputeNodeTree(node)
554         if len(child_result) == 1:
555           # child (and all its descendants) is simple, doesn't split
556           # over multiple hosts, so we don't need to describe it, our
557           # own entry for this node describes it completely
558           continue
559         else:
560           # check if child nodes differ from my nodes; note that
561           # subdisk can differ from the child itself, and be instead
562           # one of its descendants
563           for subnode, subdisk in child_result:
564             if subnode not in my_nodes:
565               result.append((subnode, subdisk))
566             # otherwise child is under our own node, so we ignore this
567             # entry (but probably the other results in the list will
568             # be different)
569     return result
570
571   def ComputeGrowth(self, amount):
572     """Compute the per-VG growth requirements.
573
574     This only works for VG-based disks.
575
576     @type amount: integer
577     @param amount: the desired increase in (user-visible) disk space
578     @rtype: dict
579     @return: a dictionary of volume-groups and the required size
580
581     """
582     if self.dev_type == constants.LD_LV:
583       return {self.logical_id[0]: amount}
584     elif self.dev_type == constants.LD_DRBD8:
585       if self.children:
586         return self.children[0].ComputeGrowth(amount)
587       else:
588         return {}
589     else:
590       # Other disk types do not require VG space
591       return {}
592
593   def RecordGrow(self, amount):
594     """Update the size of this disk after growth.
595
596     This method recurses over the disks's children and updates their
597     size correspondigly. The method needs to be kept in sync with the
598     actual algorithms from bdev.
599
600     """
601     if self.dev_type in (constants.LD_LV, constants.LD_FILE):
602       self.size += amount
603     elif self.dev_type == constants.LD_DRBD8:
604       if self.children:
605         self.children[0].RecordGrow(amount)
606       self.size += amount
607     else:
608       raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
609                                    " disk type %s" % self.dev_type)
610
611   def UnsetSize(self):
612     """Sets recursively the size to zero for the disk and its children.
613
614     """
615     if self.children:
616       for child in self.children:
617         child.UnsetSize()
618     self.size = 0
619
620   def SetPhysicalID(self, target_node, nodes_ip):
621     """Convert the logical ID to the physical ID.
622
623     This is used only for drbd, which needs ip/port configuration.
624
625     The routine descends down and updates its children also, because
626     this helps when the only the top device is passed to the remote
627     node.
628
629     Arguments:
630       - target_node: the node we wish to configure for
631       - nodes_ip: a mapping of node name to ip
632
633     The target_node must exist in in nodes_ip, and must be one of the
634     nodes in the logical ID for each of the DRBD devices encountered
635     in the disk tree.
636
637     """
638     if self.children:
639       for child in self.children:
640         child.SetPhysicalID(target_node, nodes_ip)
641
642     if self.logical_id is None and self.physical_id is not None:
643       return
644     if self.dev_type in constants.LDS_DRBD:
645       pnode, snode, port, pminor, sminor, secret = self.logical_id
646       if target_node not in (pnode, snode):
647         raise errors.ConfigurationError("DRBD device not knowing node %s" %
648                                         target_node)
649       pnode_ip = nodes_ip.get(pnode, None)
650       snode_ip = nodes_ip.get(snode, None)
651       if pnode_ip is None or snode_ip is None:
652         raise errors.ConfigurationError("Can't find primary or secondary node"
653                                         " for %s" % str(self))
654       p_data = (pnode_ip, port)
655       s_data = (snode_ip, port)
656       if pnode == target_node:
657         self.physical_id = p_data + s_data + (pminor, secret)
658       else: # it must be secondary, we tested above
659         self.physical_id = s_data + p_data + (sminor, secret)
660     else:
661       self.physical_id = self.logical_id
662     return
663
664   def ToDict(self):
665     """Disk-specific conversion to standard python types.
666
667     This replaces the children lists of objects with lists of
668     standard python types.
669
670     """
671     bo = super(Disk, self).ToDict()
672
673     for attr in ("children",):
674       alist = bo.get(attr, None)
675       if alist:
676         bo[attr] = self._ContainerToDicts(alist)
677     return bo
678
679   @classmethod
680   def FromDict(cls, val):
681     """Custom function for Disks
682
683     """
684     obj = super(Disk, cls).FromDict(val)
685     if obj.children:
686       obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
687     if obj.logical_id and isinstance(obj.logical_id, list):
688       obj.logical_id = tuple(obj.logical_id)
689     if obj.physical_id and isinstance(obj.physical_id, list):
690       obj.physical_id = tuple(obj.physical_id)
691     if obj.dev_type in constants.LDS_DRBD:
692       # we need a tuple of length six here
693       if len(obj.logical_id) < 6:
694         obj.logical_id += (None,) * (6 - len(obj.logical_id))
695     return obj
696
697   def __str__(self):
698     """Custom str() formatter for disks.
699
700     """
701     if self.dev_type == constants.LD_LV:
702       val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
703     elif self.dev_type in constants.LDS_DRBD:
704       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
705       val = "<DRBD8("
706       if self.physical_id is None:
707         phy = "unconfigured"
708       else:
709         phy = ("configured as %s:%s %s:%s" %
710                (self.physical_id[0], self.physical_id[1],
711                 self.physical_id[2], self.physical_id[3]))
712
713       val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
714               (node_a, minor_a, node_b, minor_b, port, phy))
715       if self.children and self.children.count(None) == 0:
716         val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
717       else:
718         val += "no local storage"
719     else:
720       val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
721              (self.dev_type, self.logical_id, self.physical_id, self.children))
722     if self.iv_name is None:
723       val += ", not visible"
724     else:
725       val += ", visible as /dev/%s" % self.iv_name
726     if isinstance(self.size, int):
727       val += ", size=%dm)>" % self.size
728     else:
729       val += ", size='%s')>" % (self.size,)
730     return val
731
732   def Verify(self):
733     """Checks that this disk is correctly configured.
734
735     """
736     all_errors = []
737     if self.mode not in constants.DISK_ACCESS_SET:
738       all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
739     return all_errors
740
741   def UpgradeConfig(self):
742     """Fill defaults for missing configuration values.
743
744     """
745     if self.children:
746       for child in self.children:
747         child.UpgradeConfig()
748     # add here config upgrade for this disk
749
750
751 class Instance(TaggableObject):
752   """Config object representing an instance."""
753   __slots__ = [
754     "name",
755     "primary_node",
756     "os",
757     "hypervisor",
758     "hvparams",
759     "beparams",
760     "osparams",
761     "admin_state",
762     "nics",
763     "disks",
764     "disk_template",
765     "network_port",
766     "serial_no",
767     ] + _TIMESTAMPS + _UUID
768
769   def _ComputeSecondaryNodes(self):
770     """Compute the list of secondary nodes.
771
772     This is a simple wrapper over _ComputeAllNodes.
773
774     """
775     all_nodes = set(self._ComputeAllNodes())
776     all_nodes.discard(self.primary_node)
777     return tuple(all_nodes)
778
779   secondary_nodes = property(_ComputeSecondaryNodes, None, None,
780                              "List of secondary nodes")
781
782   def _ComputeAllNodes(self):
783     """Compute the list of all nodes.
784
785     Since the data is already there (in the drbd disks), keeping it as
786     a separate normal attribute is redundant and if not properly
787     synchronised can cause problems. Thus it's better to compute it
788     dynamically.
789
790     """
791     def _Helper(nodes, device):
792       """Recursively computes nodes given a top device."""
793       if device.dev_type in constants.LDS_DRBD:
794         nodea, nodeb = device.logical_id[:2]
795         nodes.add(nodea)
796         nodes.add(nodeb)
797       if device.children:
798         for child in device.children:
799           _Helper(nodes, child)
800
801     all_nodes = set()
802     all_nodes.add(self.primary_node)
803     for device in self.disks:
804       _Helper(all_nodes, device)
805     return tuple(all_nodes)
806
807   all_nodes = property(_ComputeAllNodes, None, None,
808                        "List of all nodes of the instance")
809
810   def MapLVsByNode(self, lvmap=None, devs=None, node=None):
811     """Provide a mapping of nodes to LVs this instance owns.
812
813     This function figures out what logical volumes should belong on
814     which nodes, recursing through a device tree.
815
816     @param lvmap: optional dictionary to receive the
817         'node' : ['lv', ...] data.
818
819     @return: None if lvmap arg is given, otherwise, a dictionary of
820         the form { 'nodename' : ['volume1', 'volume2', ...], ... };
821         volumeN is of the form "vg_name/lv_name", compatible with
822         GetVolumeList()
823
824     """
825     if node == None:
826       node = self.primary_node
827
828     if lvmap is None:
829       lvmap = {
830         node: [],
831         }
832       ret = lvmap
833     else:
834       if not node in lvmap:
835         lvmap[node] = []
836       ret = None
837
838     if not devs:
839       devs = self.disks
840
841     for dev in devs:
842       if dev.dev_type == constants.LD_LV:
843         lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
844
845       elif dev.dev_type in constants.LDS_DRBD:
846         if dev.children:
847           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
848           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
849
850       elif dev.children:
851         self.MapLVsByNode(lvmap, dev.children, node)
852
853     return ret
854
855   def FindDisk(self, idx):
856     """Find a disk given having a specified index.
857
858     This is just a wrapper that does validation of the index.
859
860     @type idx: int
861     @param idx: the disk index
862     @rtype: L{Disk}
863     @return: the corresponding disk
864     @raise errors.OpPrereqError: when the given index is not valid
865
866     """
867     try:
868       idx = int(idx)
869       return self.disks[idx]
870     except (TypeError, ValueError), err:
871       raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
872                                  errors.ECODE_INVAL)
873     except IndexError:
874       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
875                                  " 0 to %d" % (idx, len(self.disks) - 1),
876                                  errors.ECODE_INVAL)
877
878   def ToDict(self):
879     """Instance-specific conversion to standard python types.
880
881     This replaces the children lists of objects with lists of standard
882     python types.
883
884     """
885     bo = super(Instance, self).ToDict()
886
887     for attr in "nics", "disks":
888       alist = bo.get(attr, None)
889       if alist:
890         nlist = self._ContainerToDicts(alist)
891       else:
892         nlist = []
893       bo[attr] = nlist
894     return bo
895
896   @classmethod
897   def FromDict(cls, val):
898     """Custom function for instances.
899
900     """
901     if "admin_state" not in val:
902       if val.get("admin_up", False):
903         val["admin_state"] = constants.ADMINST_UP
904       else:
905         val["admin_state"] = constants.ADMINST_DOWN
906     if "admin_up" in val:
907       del val["admin_up"]
908     obj = super(Instance, cls).FromDict(val)
909     obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
910     obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
911     return obj
912
913   def UpgradeConfig(self):
914     """Fill defaults for missing configuration values.
915
916     """
917     for nic in self.nics:
918       nic.UpgradeConfig()
919     for disk in self.disks:
920       disk.UpgradeConfig()
921     if self.hvparams:
922       for key in constants.HVC_GLOBALS:
923         try:
924           del self.hvparams[key]
925         except KeyError:
926           pass
927     if self.osparams is None:
928       self.osparams = {}
929     UpgradeBeParams(self.beparams)
930
931
932 class OS(ConfigObject):
933   """Config object representing an operating system.
934
935   @type supported_parameters: list
936   @ivar supported_parameters: a list of tuples, name and description,
937       containing the supported parameters by this OS
938
939   @type VARIANT_DELIM: string
940   @cvar VARIANT_DELIM: the variant delimiter
941
942   """
943   __slots__ = [
944     "name",
945     "path",
946     "api_versions",
947     "create_script",
948     "export_script",
949     "import_script",
950     "rename_script",
951     "verify_script",
952     "supported_variants",
953     "supported_parameters",
954     ]
955
956   VARIANT_DELIM = "+"
957
958   @classmethod
959   def SplitNameVariant(cls, name):
960     """Splits the name into the proper name and variant.
961
962     @param name: the OS (unprocessed) name
963     @rtype: list
964     @return: a list of two elements; if the original name didn't
965         contain a variant, it's returned as an empty string
966
967     """
968     nv = name.split(cls.VARIANT_DELIM, 1)
969     if len(nv) == 1:
970       nv.append("")
971     return nv
972
973   @classmethod
974   def GetName(cls, name):
975     """Returns the proper name of the os (without the variant).
976
977     @param name: the OS (unprocessed) name
978
979     """
980     return cls.SplitNameVariant(name)[0]
981
982   @classmethod
983   def GetVariant(cls, name):
984     """Returns the variant the os (without the base name).
985
986     @param name: the OS (unprocessed) name
987
988     """
989     return cls.SplitNameVariant(name)[1]
990
991
992 class Node(TaggableObject):
993   """Config object representing a node."""
994   __slots__ = [
995     "name",
996     "primary_ip",
997     "secondary_ip",
998     "serial_no",
999     "master_candidate",
1000     "offline",
1001     "drained",
1002     "group",
1003     "master_capable",
1004     "vm_capable",
1005     "ndparams",
1006     "powered",
1007     "hv_state",
1008     "disk_state",
1009     ] + _TIMESTAMPS + _UUID
1010
1011   def UpgradeConfig(self):
1012     """Fill defaults for missing configuration values.
1013
1014     """
1015     # pylint: disable=E0203
1016     # because these are "defined" via slots, not manually
1017     if self.master_capable is None:
1018       self.master_capable = True
1019
1020     if self.vm_capable is None:
1021       self.vm_capable = True
1022
1023     if self.ndparams is None:
1024       self.ndparams = {}
1025
1026     if self.powered is None:
1027       self.powered = True
1028
1029
1030 class NodeGroup(TaggableObject):
1031   """Config object representing a node group."""
1032   __slots__ = [
1033     "name",
1034     "members",
1035     "ndparams",
1036     "serial_no",
1037     "alloc_policy",
1038     ] + _TIMESTAMPS + _UUID
1039
1040   def ToDict(self):
1041     """Custom function for nodegroup.
1042
1043     This discards the members object, which gets recalculated and is only kept
1044     in memory.
1045
1046     """
1047     mydict = super(NodeGroup, self).ToDict()
1048     del mydict["members"]
1049     return mydict
1050
1051   @classmethod
1052   def FromDict(cls, val):
1053     """Custom function for nodegroup.
1054
1055     The members slot is initialized to an empty list, upon deserialization.
1056
1057     """
1058     obj = super(NodeGroup, cls).FromDict(val)
1059     obj.members = []
1060     return obj
1061
1062   def UpgradeConfig(self):
1063     """Fill defaults for missing configuration values.
1064
1065     """
1066     if self.ndparams is None:
1067       self.ndparams = {}
1068
1069     if self.serial_no is None:
1070       self.serial_no = 1
1071
1072     if self.alloc_policy is None:
1073       self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1074
1075     # We only update mtime, and not ctime, since we would not be able to provide
1076     # a correct value for creation time.
1077     if self.mtime is None:
1078       self.mtime = time.time()
1079
1080   def FillND(self, node):
1081     """Return filled out ndparams for L{objects.Node}
1082
1083     @type node: L{objects.Node}
1084     @param node: A Node object to fill
1085     @return a copy of the node's ndparams with defaults filled
1086
1087     """
1088     return self.SimpleFillND(node.ndparams)
1089
1090   def SimpleFillND(self, ndparams):
1091     """Fill a given ndparams dict with defaults.
1092
1093     @type ndparams: dict
1094     @param ndparams: the dict to fill
1095     @rtype: dict
1096     @return: a copy of the passed in ndparams with missing keys filled
1097         from the node group defaults
1098
1099     """
1100     return FillDict(self.ndparams, ndparams)
1101
1102
1103 class Cluster(TaggableObject):
1104   """Config object representing the cluster."""
1105   __slots__ = [
1106     "serial_no",
1107     "rsahostkeypub",
1108     "highest_used_port",
1109     "tcpudp_port_pool",
1110     "mac_prefix",
1111     "volume_group_name",
1112     "reserved_lvs",
1113     "drbd_usermode_helper",
1114     "default_bridge",
1115     "default_hypervisor",
1116     "master_node",
1117     "master_ip",
1118     "master_netdev",
1119     "master_netmask",
1120     "use_external_mip_script",
1121     "cluster_name",
1122     "file_storage_dir",
1123     "shared_file_storage_dir",
1124     "enabled_hypervisors",
1125     "hvparams",
1126     "os_hvp",
1127     "beparams",
1128     "osparams",
1129     "nicparams",
1130     "ndparams",
1131     "candidate_pool_size",
1132     "modify_etc_hosts",
1133     "modify_ssh_setup",
1134     "maintain_node_health",
1135     "uid_pool",
1136     "default_iallocator",
1137     "hidden_os",
1138     "blacklisted_os",
1139     "primary_ip_family",
1140     "prealloc_wipe_disks",
1141     ] + _TIMESTAMPS + _UUID
1142
1143   def UpgradeConfig(self):
1144     """Fill defaults for missing configuration values.
1145
1146     """
1147     # pylint: disable=E0203
1148     # because these are "defined" via slots, not manually
1149     if self.hvparams is None:
1150       self.hvparams = constants.HVC_DEFAULTS
1151     else:
1152       for hypervisor in self.hvparams:
1153         self.hvparams[hypervisor] = FillDict(
1154             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1155
1156     if self.os_hvp is None:
1157       self.os_hvp = {}
1158
1159     # osparams added before 2.2
1160     if self.osparams is None:
1161       self.osparams = {}
1162
1163     if self.ndparams is None:
1164       self.ndparams = constants.NDC_DEFAULTS
1165
1166     self.beparams = UpgradeGroupedParams(self.beparams,
1167                                          constants.BEC_DEFAULTS)
1168     for beparams_group in self.beparams:
1169       UpgradeBeParams(self.beparams[beparams_group])
1170
1171     migrate_default_bridge = not self.nicparams
1172     self.nicparams = UpgradeGroupedParams(self.nicparams,
1173                                           constants.NICC_DEFAULTS)
1174     if migrate_default_bridge:
1175       self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1176         self.default_bridge
1177
1178     if self.modify_etc_hosts is None:
1179       self.modify_etc_hosts = True
1180
1181     if self.modify_ssh_setup is None:
1182       self.modify_ssh_setup = True
1183
1184     # default_bridge is no longer used in 2.1. The slot is left there to
1185     # support auto-upgrading. It can be removed once we decide to deprecate
1186     # upgrading straight from 2.0.
1187     if self.default_bridge is not None:
1188       self.default_bridge = None
1189
1190     # default_hypervisor is just the first enabled one in 2.1. This slot and
1191     # code can be removed once upgrading straight from 2.0 is deprecated.
1192     if self.default_hypervisor is not None:
1193       self.enabled_hypervisors = ([self.default_hypervisor] +
1194         [hvname for hvname in self.enabled_hypervisors
1195          if hvname != self.default_hypervisor])
1196       self.default_hypervisor = None
1197
1198     # maintain_node_health added after 2.1.1
1199     if self.maintain_node_health is None:
1200       self.maintain_node_health = False
1201
1202     if self.uid_pool is None:
1203       self.uid_pool = []
1204
1205     if self.default_iallocator is None:
1206       self.default_iallocator = ""
1207
1208     # reserved_lvs added before 2.2
1209     if self.reserved_lvs is None:
1210       self.reserved_lvs = []
1211
1212     # hidden and blacklisted operating systems added before 2.2.1
1213     if self.hidden_os is None:
1214       self.hidden_os = []
1215
1216     if self.blacklisted_os is None:
1217       self.blacklisted_os = []
1218
1219     # primary_ip_family added before 2.3
1220     if self.primary_ip_family is None:
1221       self.primary_ip_family = AF_INET
1222
1223     if self.master_netmask is None:
1224       ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1225       self.master_netmask = ipcls.iplen
1226
1227     if self.prealloc_wipe_disks is None:
1228       self.prealloc_wipe_disks = False
1229
1230     # shared_file_storage_dir added before 2.5
1231     if self.shared_file_storage_dir is None:
1232       self.shared_file_storage_dir = ""
1233
1234     if self.use_external_mip_script is None:
1235       self.use_external_mip_script = False
1236
1237   def ToDict(self):
1238     """Custom function for cluster.
1239
1240     """
1241     mydict = super(Cluster, self).ToDict()
1242     mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1243     return mydict
1244
1245   @classmethod
1246   def FromDict(cls, val):
1247     """Custom function for cluster.
1248
1249     """
1250     obj = super(Cluster, cls).FromDict(val)
1251     if not isinstance(obj.tcpudp_port_pool, set):
1252       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1253     return obj
1254
1255   def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1256     """Get the default hypervisor parameters for the cluster.
1257
1258     @param hypervisor: the hypervisor name
1259     @param os_name: if specified, we'll also update the defaults for this OS
1260     @param skip_keys: if passed, list of keys not to use
1261     @return: the defaults dict
1262
1263     """
1264     if skip_keys is None:
1265       skip_keys = []
1266
1267     fill_stack = [self.hvparams.get(hypervisor, {})]
1268     if os_name is not None:
1269       os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1270       fill_stack.append(os_hvp)
1271
1272     ret_dict = {}
1273     for o_dict in fill_stack:
1274       ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1275
1276     return ret_dict
1277
1278   def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1279     """Fill a given hvparams dict with cluster defaults.
1280
1281     @type hv_name: string
1282     @param hv_name: the hypervisor to use
1283     @type os_name: string
1284     @param os_name: the OS to use for overriding the hypervisor defaults
1285     @type skip_globals: boolean
1286     @param skip_globals: if True, the global hypervisor parameters will
1287         not be filled
1288     @rtype: dict
1289     @return: a copy of the given hvparams with missing keys filled from
1290         the cluster defaults
1291
1292     """
1293     if skip_globals:
1294       skip_keys = constants.HVC_GLOBALS
1295     else:
1296       skip_keys = []
1297
1298     def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1299     return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1300
1301   def FillHV(self, instance, skip_globals=False):
1302     """Fill an instance's hvparams dict with cluster defaults.
1303
1304     @type instance: L{objects.Instance}
1305     @param instance: the instance parameter to fill
1306     @type skip_globals: boolean
1307     @param skip_globals: if True, the global hypervisor parameters will
1308         not be filled
1309     @rtype: dict
1310     @return: a copy of the instance's hvparams with missing keys filled from
1311         the cluster defaults
1312
1313     """
1314     return self.SimpleFillHV(instance.hypervisor, instance.os,
1315                              instance.hvparams, skip_globals)
1316
1317   def SimpleFillBE(self, beparams):
1318     """Fill a given beparams dict with cluster defaults.
1319
1320     @type beparams: dict
1321     @param beparams: the dict to fill
1322     @rtype: dict
1323     @return: a copy of the passed in beparams with missing keys filled
1324         from the cluster defaults
1325
1326     """
1327     return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1328
1329   def FillBE(self, instance):
1330     """Fill an instance's beparams dict with cluster defaults.
1331
1332     @type instance: L{objects.Instance}
1333     @param instance: the instance parameter to fill
1334     @rtype: dict
1335     @return: a copy of the instance's beparams with missing keys filled from
1336         the cluster defaults
1337
1338     """
1339     return self.SimpleFillBE(instance.beparams)
1340
1341   def SimpleFillNIC(self, nicparams):
1342     """Fill a given nicparams dict with cluster defaults.
1343
1344     @type nicparams: dict
1345     @param nicparams: the dict to fill
1346     @rtype: dict
1347     @return: a copy of the passed in nicparams with missing keys filled
1348         from the cluster defaults
1349
1350     """
1351     return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1352
1353   def SimpleFillOS(self, os_name, os_params):
1354     """Fill an instance's osparams dict with cluster defaults.
1355
1356     @type os_name: string
1357     @param os_name: the OS name to use
1358     @type os_params: dict
1359     @param os_params: the dict to fill with default values
1360     @rtype: dict
1361     @return: a copy of the instance's osparams with missing keys filled from
1362         the cluster defaults
1363
1364     """
1365     name_only = os_name.split("+", 1)[0]
1366     # base OS
1367     result = self.osparams.get(name_only, {})
1368     # OS with variant
1369     result = FillDict(result, self.osparams.get(os_name, {}))
1370     # specified params
1371     return FillDict(result, os_params)
1372
1373   def FillND(self, node, nodegroup):
1374     """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1375
1376     @type node: L{objects.Node}
1377     @param node: A Node object to fill
1378     @type nodegroup: L{objects.NodeGroup}
1379     @param nodegroup: A Node object to fill
1380     @return a copy of the node's ndparams with defaults filled
1381
1382     """
1383     return self.SimpleFillND(nodegroup.FillND(node))
1384
1385   def SimpleFillND(self, ndparams):
1386     """Fill a given ndparams dict with defaults.
1387
1388     @type ndparams: dict
1389     @param ndparams: the dict to fill
1390     @rtype: dict
1391     @return: a copy of the passed in ndparams with missing keys filled
1392         from the cluster defaults
1393
1394     """
1395     return FillDict(self.ndparams, ndparams)
1396
1397
1398 class BlockDevStatus(ConfigObject):
1399   """Config object representing the status of a block device."""
1400   __slots__ = [
1401     "dev_path",
1402     "major",
1403     "minor",
1404     "sync_percent",
1405     "estimated_time",
1406     "is_degraded",
1407     "ldisk_status",
1408     ]
1409
1410
1411 class ImportExportStatus(ConfigObject):
1412   """Config object representing the status of an import or export."""
1413   __slots__ = [
1414     "recent_output",
1415     "listen_port",
1416     "connected",
1417     "progress_mbytes",
1418     "progress_throughput",
1419     "progress_eta",
1420     "progress_percent",
1421     "exit_status",
1422     "error_message",
1423     ] + _TIMESTAMPS
1424
1425
1426 class ImportExportOptions(ConfigObject):
1427   """Options for import/export daemon
1428
1429   @ivar key_name: X509 key name (None for cluster certificate)
1430   @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1431   @ivar compress: Compression method (one of L{constants.IEC_ALL})
1432   @ivar magic: Used to ensure the connection goes to the right disk
1433   @ivar ipv6: Whether to use IPv6
1434   @ivar connect_timeout: Number of seconds for establishing connection
1435
1436   """
1437   __slots__ = [
1438     "key_name",
1439     "ca_pem",
1440     "compress",
1441     "magic",
1442     "ipv6",
1443     "connect_timeout",
1444     ]
1445
1446
1447 class ConfdRequest(ConfigObject):
1448   """Object holding a confd request.
1449
1450   @ivar protocol: confd protocol version
1451   @ivar type: confd query type
1452   @ivar query: query request
1453   @ivar rsalt: requested reply salt
1454
1455   """
1456   __slots__ = [
1457     "protocol",
1458     "type",
1459     "query",
1460     "rsalt",
1461     ]
1462
1463
1464 class ConfdReply(ConfigObject):
1465   """Object holding a confd reply.
1466
1467   @ivar protocol: confd protocol version
1468   @ivar status: reply status code (ok, error)
1469   @ivar answer: confd query reply
1470   @ivar serial: configuration serial number
1471
1472   """
1473   __slots__ = [
1474     "protocol",
1475     "status",
1476     "answer",
1477     "serial",
1478     ]
1479
1480
1481 class QueryFieldDefinition(ConfigObject):
1482   """Object holding a query field definition.
1483
1484   @ivar name: Field name
1485   @ivar title: Human-readable title
1486   @ivar kind: Field type
1487   @ivar doc: Human-readable description
1488
1489   """
1490   __slots__ = [
1491     "name",
1492     "title",
1493     "kind",
1494     "doc",
1495     ]
1496
1497
1498 class _QueryResponseBase(ConfigObject):
1499   __slots__ = [
1500     "fields",
1501     ]
1502
1503   def ToDict(self):
1504     """Custom function for serializing.
1505
1506     """
1507     mydict = super(_QueryResponseBase, self).ToDict()
1508     mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1509     return mydict
1510
1511   @classmethod
1512   def FromDict(cls, val):
1513     """Custom function for de-serializing.
1514
1515     """
1516     obj = super(_QueryResponseBase, cls).FromDict(val)
1517     obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1518     return obj
1519
1520
1521 class QueryRequest(ConfigObject):
1522   """Object holding a query request.
1523
1524   """
1525   __slots__ = [
1526     "what",
1527     "fields",
1528     "qfilter",
1529     ]
1530
1531
1532 class QueryResponse(_QueryResponseBase):
1533   """Object holding the response to a query.
1534
1535   @ivar fields: List of L{QueryFieldDefinition} objects
1536   @ivar data: Requested data
1537
1538   """
1539   __slots__ = [
1540     "data",
1541     ]
1542
1543
1544 class QueryFieldsRequest(ConfigObject):
1545   """Object holding a request for querying available fields.
1546
1547   """
1548   __slots__ = [
1549     "what",
1550     "fields",
1551     ]
1552
1553
1554 class QueryFieldsResponse(_QueryResponseBase):
1555   """Object holding the response to a query for fields.
1556
1557   @ivar fields: List of L{QueryFieldDefinition} objects
1558
1559   """
1560   __slots__ = [
1561     ]
1562
1563
1564 class MigrationStatus(ConfigObject):
1565   """Object holding the status of a migration.
1566
1567   """
1568   __slots__ = [
1569     "status",
1570     "transferred_ram",
1571     "total_ram",
1572     ]
1573
1574
1575 class InstanceConsole(ConfigObject):
1576   """Object describing how to access the console of an instance.
1577
1578   """
1579   __slots__ = [
1580     "instance",
1581     "kind",
1582     "message",
1583     "host",
1584     "port",
1585     "user",
1586     "command",
1587     "display",
1588     ]
1589
1590   def Validate(self):
1591     """Validates contents of this object.
1592
1593     """
1594     assert self.kind in constants.CONS_ALL, "Unknown console type"
1595     assert self.instance, "Missing instance name"
1596     assert self.message or self.kind in [constants.CONS_SSH,
1597                                          constants.CONS_SPICE,
1598                                          constants.CONS_VNC]
1599     assert self.host or self.kind == constants.CONS_MESSAGE
1600     assert self.port or self.kind in [constants.CONS_MESSAGE,
1601                                       constants.CONS_SSH]
1602     assert self.user or self.kind in [constants.CONS_MESSAGE,
1603                                       constants.CONS_SPICE,
1604                                       constants.CONS_VNC]
1605     assert self.command or self.kind in [constants.CONS_MESSAGE,
1606                                          constants.CONS_SPICE,
1607                                          constants.CONS_VNC]
1608     assert self.display or self.kind in [constants.CONS_MESSAGE,
1609                                          constants.CONS_SPICE,
1610                                          constants.CONS_SSH]
1611     return True
1612
1613
1614 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1615   """Simple wrapper over ConfigParse that allows serialization.
1616
1617   This class is basically ConfigParser.SafeConfigParser with two
1618   additional methods that allow it to serialize/unserialize to/from a
1619   buffer.
1620
1621   """
1622   def Dumps(self):
1623     """Dump this instance and return the string representation."""
1624     buf = StringIO()
1625     self.write(buf)
1626     return buf.getvalue()
1627
1628   @classmethod
1629   def Loads(cls, data):
1630     """Load data from a string."""
1631     buf = StringIO(data)
1632     cfp = cls()
1633     cfp.readfp(buf)
1634     return cfp