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