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