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