Rename ispecs-disk-templates to ipolicy-disk-templates
[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_PARAMETERS:
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 UnsetSize(self):
734     """Sets recursively the size to zero for the disk and its children.
735
736     """
737     if self.children:
738       for child in self.children:
739         child.UnsetSize()
740     self.size = 0
741
742   def SetPhysicalID(self, target_node, nodes_ip):
743     """Convert the logical ID to the physical ID.
744
745     This is used only for drbd, which needs ip/port configuration.
746
747     The routine descends down and updates its children also, because
748     this helps when the only the top device is passed to the remote
749     node.
750
751     Arguments:
752       - target_node: the node we wish to configure for
753       - nodes_ip: a mapping of node name to ip
754
755     The target_node must exist in in nodes_ip, and must be one of the
756     nodes in the logical ID for each of the DRBD devices encountered
757     in the disk tree.
758
759     """
760     if self.children:
761       for child in self.children:
762         child.SetPhysicalID(target_node, nodes_ip)
763
764     if self.logical_id is None and self.physical_id is not None:
765       return
766     if self.dev_type in constants.LDS_DRBD:
767       pnode, snode, port, pminor, sminor, secret = self.logical_id
768       if target_node not in (pnode, snode):
769         raise errors.ConfigurationError("DRBD device not knowing node %s" %
770                                         target_node)
771       pnode_ip = nodes_ip.get(pnode, None)
772       snode_ip = nodes_ip.get(snode, None)
773       if pnode_ip is None or snode_ip is None:
774         raise errors.ConfigurationError("Can't find primary or secondary node"
775                                         " for %s" % str(self))
776       p_data = (pnode_ip, port)
777       s_data = (snode_ip, port)
778       if pnode == target_node:
779         self.physical_id = p_data + s_data + (pminor, secret)
780       else: # it must be secondary, we tested above
781         self.physical_id = s_data + p_data + (sminor, secret)
782     else:
783       self.physical_id = self.logical_id
784     return
785
786   def ToDict(self):
787     """Disk-specific conversion to standard python types.
788
789     This replaces the children lists of objects with lists of
790     standard python types.
791
792     """
793     bo = super(Disk, self).ToDict()
794
795     for attr in ("children",):
796       alist = bo.get(attr, None)
797       if alist:
798         bo[attr] = self._ContainerToDicts(alist)
799     return bo
800
801   @classmethod
802   def FromDict(cls, val):
803     """Custom function for Disks
804
805     """
806     obj = super(Disk, cls).FromDict(val)
807     if obj.children:
808       obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
809     if obj.logical_id and isinstance(obj.logical_id, list):
810       obj.logical_id = tuple(obj.logical_id)
811     if obj.physical_id and isinstance(obj.physical_id, list):
812       obj.physical_id = tuple(obj.physical_id)
813     if obj.dev_type in constants.LDS_DRBD:
814       # we need a tuple of length six here
815       if len(obj.logical_id) < 6:
816         obj.logical_id += (None,) * (6 - len(obj.logical_id))
817     return obj
818
819   def __str__(self):
820     """Custom str() formatter for disks.
821
822     """
823     if self.dev_type == constants.LD_LV:
824       val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
825     elif self.dev_type in constants.LDS_DRBD:
826       node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
827       val = "<DRBD8("
828       if self.physical_id is None:
829         phy = "unconfigured"
830       else:
831         phy = ("configured as %s:%s %s:%s" %
832                (self.physical_id[0], self.physical_id[1],
833                 self.physical_id[2], self.physical_id[3]))
834
835       val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
836               (node_a, minor_a, node_b, minor_b, port, phy))
837       if self.children and self.children.count(None) == 0:
838         val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
839       else:
840         val += "no local storage"
841     else:
842       val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
843              (self.dev_type, self.logical_id, self.physical_id, self.children))
844     if self.iv_name is None:
845       val += ", not visible"
846     else:
847       val += ", visible as /dev/%s" % self.iv_name
848     if isinstance(self.size, int):
849       val += ", size=%dm)>" % self.size
850     else:
851       val += ", size='%s')>" % (self.size,)
852     return val
853
854   def Verify(self):
855     """Checks that this disk is correctly configured.
856
857     """
858     all_errors = []
859     if self.mode not in constants.DISK_ACCESS_SET:
860       all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
861     return all_errors
862
863   def UpgradeConfig(self):
864     """Fill defaults for missing configuration values.
865
866     """
867     if self.children:
868       for child in self.children:
869         child.UpgradeConfig()
870
871     if not self.params:
872       self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
873     else:
874       self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
875                              self.params)
876     # add here config upgrade for this disk
877
878
879 class InstancePolicy(ConfigObject):
880   """Config object representing instance policy limits dictionary."""
881   __slots__ = ["min", "max", "std", "disk_templates"]
882
883   @classmethod
884   def CheckParameterSyntax(cls, ipolicy):
885     """ Check the instance policy for validity.
886
887     """
888     for param in constants.ISPECS_PARAMETERS:
889       InstancePolicy.CheckISpecSyntax(ipolicy, param)
890     if constants.IPOLICY_DTS in ipolicy:
891       InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
892     wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
893     if wrong_keys:
894       raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
895                                       utils.CommaJoin(wrong_keys))
896
897   @classmethod
898   def CheckISpecSyntax(cls, ipolicy, name):
899     """Check the instance policy for validity on a given key.
900
901     We check if the instance policy makes sense for a given key, that is
902     if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
903
904     @type ipolicy: dict
905     @param ipolicy: dictionary with min, max, std specs
906     @type name: string
907     @param name: what are the limits for
908     @raise errors.ConfigureError: when specs for given name are not valid
909
910     """
911     min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
912     std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
913     max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
914     err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
915            (name,
916             ipolicy[constants.ISPECS_MIN].get(name, "-"),
917             ipolicy[constants.ISPECS_MAX].get(name, "-"),
918             ipolicy[constants.ISPECS_STD].get(name, "-")))
919     if min_v > std_v or std_v > max_v:
920       raise errors.ConfigurationError(err)
921
922   @classmethod
923   def CheckDiskTemplates(cls, disk_templates):
924     """Checks the disk templates for validity.
925
926     """
927     wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
928     if wrong:
929       raise errors.ConfigurationError("Invalid disk template(s) %s" %
930                                       utils.CommaJoin(wrong))
931
932
933 class Instance(TaggableObject):
934   """Config object representing an instance."""
935   __slots__ = [
936     "name",
937     "primary_node",
938     "os",
939     "hypervisor",
940     "hvparams",
941     "beparams",
942     "osparams",
943     "admin_state",
944     "nics",
945     "disks",
946     "disk_template",
947     "network_port",
948     "serial_no",
949     ] + _TIMESTAMPS + _UUID
950
951   def _ComputeSecondaryNodes(self):
952     """Compute the list of secondary nodes.
953
954     This is a simple wrapper over _ComputeAllNodes.
955
956     """
957     all_nodes = set(self._ComputeAllNodes())
958     all_nodes.discard(self.primary_node)
959     return tuple(all_nodes)
960
961   secondary_nodes = property(_ComputeSecondaryNodes, None, None,
962                              "List of secondary nodes")
963
964   def _ComputeAllNodes(self):
965     """Compute the list of all nodes.
966
967     Since the data is already there (in the drbd disks), keeping it as
968     a separate normal attribute is redundant and if not properly
969     synchronised can cause problems. Thus it's better to compute it
970     dynamically.
971
972     """
973     def _Helper(nodes, device):
974       """Recursively computes nodes given a top device."""
975       if device.dev_type in constants.LDS_DRBD:
976         nodea, nodeb = device.logical_id[:2]
977         nodes.add(nodea)
978         nodes.add(nodeb)
979       if device.children:
980         for child in device.children:
981           _Helper(nodes, child)
982
983     all_nodes = set()
984     all_nodes.add(self.primary_node)
985     for device in self.disks:
986       _Helper(all_nodes, device)
987     return tuple(all_nodes)
988
989   all_nodes = property(_ComputeAllNodes, None, None,
990                        "List of all nodes of the instance")
991
992   def MapLVsByNode(self, lvmap=None, devs=None, node=None):
993     """Provide a mapping of nodes to LVs this instance owns.
994
995     This function figures out what logical volumes should belong on
996     which nodes, recursing through a device tree.
997
998     @param lvmap: optional dictionary to receive the
999         'node' : ['lv', ...] data.
1000
1001     @return: None if lvmap arg is given, otherwise, a dictionary of
1002         the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1003         volumeN is of the form "vg_name/lv_name", compatible with
1004         GetVolumeList()
1005
1006     """
1007     if node == None:
1008       node = self.primary_node
1009
1010     if lvmap is None:
1011       lvmap = {
1012         node: [],
1013         }
1014       ret = lvmap
1015     else:
1016       if not node in lvmap:
1017         lvmap[node] = []
1018       ret = None
1019
1020     if not devs:
1021       devs = self.disks
1022
1023     for dev in devs:
1024       if dev.dev_type == constants.LD_LV:
1025         lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1026
1027       elif dev.dev_type in constants.LDS_DRBD:
1028         if dev.children:
1029           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1030           self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1031
1032       elif dev.children:
1033         self.MapLVsByNode(lvmap, dev.children, node)
1034
1035     return ret
1036
1037   def FindDisk(self, idx):
1038     """Find a disk given having a specified index.
1039
1040     This is just a wrapper that does validation of the index.
1041
1042     @type idx: int
1043     @param idx: the disk index
1044     @rtype: L{Disk}
1045     @return: the corresponding disk
1046     @raise errors.OpPrereqError: when the given index is not valid
1047
1048     """
1049     try:
1050       idx = int(idx)
1051       return self.disks[idx]
1052     except (TypeError, ValueError), err:
1053       raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1054                                  errors.ECODE_INVAL)
1055     except IndexError:
1056       raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1057                                  " 0 to %d" % (idx, len(self.disks) - 1),
1058                                  errors.ECODE_INVAL)
1059
1060   def ToDict(self):
1061     """Instance-specific conversion to standard python types.
1062
1063     This replaces the children lists of objects with lists of standard
1064     python types.
1065
1066     """
1067     bo = super(Instance, self).ToDict()
1068
1069     for attr in "nics", "disks":
1070       alist = bo.get(attr, None)
1071       if alist:
1072         nlist = self._ContainerToDicts(alist)
1073       else:
1074         nlist = []
1075       bo[attr] = nlist
1076     return bo
1077
1078   @classmethod
1079   def FromDict(cls, val):
1080     """Custom function for instances.
1081
1082     """
1083     if "admin_state" not in val:
1084       if val.get("admin_up", False):
1085         val["admin_state"] = constants.ADMINST_UP
1086       else:
1087         val["admin_state"] = constants.ADMINST_DOWN
1088     if "admin_up" in val:
1089       del val["admin_up"]
1090     obj = super(Instance, cls).FromDict(val)
1091     obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1092     obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1093     return obj
1094
1095   def UpgradeConfig(self):
1096     """Fill defaults for missing configuration values.
1097
1098     """
1099     for nic in self.nics:
1100       nic.UpgradeConfig()
1101     for disk in self.disks:
1102       disk.UpgradeConfig()
1103     if self.hvparams:
1104       for key in constants.HVC_GLOBALS:
1105         try:
1106           del self.hvparams[key]
1107         except KeyError:
1108           pass
1109     if self.osparams is None:
1110       self.osparams = {}
1111     UpgradeBeParams(self.beparams)
1112
1113
1114 class OS(ConfigObject):
1115   """Config object representing an operating system.
1116
1117   @type supported_parameters: list
1118   @ivar supported_parameters: a list of tuples, name and description,
1119       containing the supported parameters by this OS
1120
1121   @type VARIANT_DELIM: string
1122   @cvar VARIANT_DELIM: the variant delimiter
1123
1124   """
1125   __slots__ = [
1126     "name",
1127     "path",
1128     "api_versions",
1129     "create_script",
1130     "export_script",
1131     "import_script",
1132     "rename_script",
1133     "verify_script",
1134     "supported_variants",
1135     "supported_parameters",
1136     ]
1137
1138   VARIANT_DELIM = "+"
1139
1140   @classmethod
1141   def SplitNameVariant(cls, name):
1142     """Splits the name into the proper name and variant.
1143
1144     @param name: the OS (unprocessed) name
1145     @rtype: list
1146     @return: a list of two elements; if the original name didn't
1147         contain a variant, it's returned as an empty string
1148
1149     """
1150     nv = name.split(cls.VARIANT_DELIM, 1)
1151     if len(nv) == 1:
1152       nv.append("")
1153     return nv
1154
1155   @classmethod
1156   def GetName(cls, name):
1157     """Returns the proper name of the os (without the variant).
1158
1159     @param name: the OS (unprocessed) name
1160
1161     """
1162     return cls.SplitNameVariant(name)[0]
1163
1164   @classmethod
1165   def GetVariant(cls, name):
1166     """Returns the variant the os (without the base name).
1167
1168     @param name: the OS (unprocessed) name
1169
1170     """
1171     return cls.SplitNameVariant(name)[1]
1172
1173
1174 class NodeHvState(ConfigObject):
1175   """Hypvervisor state on a node.
1176
1177   @ivar mem_total: Total amount of memory
1178   @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1179     available)
1180   @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1181     rounding
1182   @ivar mem_inst: Memory used by instances living on node
1183   @ivar cpu_total: Total node CPU core count
1184   @ivar cpu_node: Number of CPU cores reserved for the node itself
1185
1186   """
1187   __slots__ = [
1188     "mem_total",
1189     "mem_node",
1190     "mem_hv",
1191     "mem_inst",
1192     "cpu_total",
1193     "cpu_node",
1194     ] + _TIMESTAMPS
1195
1196
1197 class NodeDiskState(ConfigObject):
1198   """Disk state on a node.
1199
1200   """
1201   __slots__ = [
1202     "total",
1203     "reserved",
1204     "overhead",
1205     ] + _TIMESTAMPS
1206
1207
1208 class Node(TaggableObject):
1209   """Config object representing a node.
1210
1211   @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1212   @ivar hv_state_static: Hypervisor state overriden by user
1213   @ivar disk_state: Disk state (e.g. free space)
1214   @ivar disk_state_static: Disk state overriden by user
1215
1216   """
1217   __slots__ = [
1218     "name",
1219     "primary_ip",
1220     "secondary_ip",
1221     "serial_no",
1222     "master_candidate",
1223     "offline",
1224     "drained",
1225     "group",
1226     "master_capable",
1227     "vm_capable",
1228     "ndparams",
1229     "powered",
1230     "hv_state",
1231     "hv_state_static",
1232     "disk_state",
1233     "disk_state_static",
1234     ] + _TIMESTAMPS + _UUID
1235
1236   def UpgradeConfig(self):
1237     """Fill defaults for missing configuration values.
1238
1239     """
1240     # pylint: disable=E0203
1241     # because these are "defined" via slots, not manually
1242     if self.master_capable is None:
1243       self.master_capable = True
1244
1245     if self.vm_capable is None:
1246       self.vm_capable = True
1247
1248     if self.ndparams is None:
1249       self.ndparams = {}
1250
1251     if self.powered is None:
1252       self.powered = True
1253
1254   def ToDict(self):
1255     """Custom function for serializing.
1256
1257     """
1258     data = super(Node, self).ToDict()
1259
1260     hv_state = data.get("hv_state", None)
1261     if hv_state is not None:
1262       data["hv_state"] = self._ContainerToDicts(hv_state)
1263
1264     disk_state = data.get("disk_state", None)
1265     if disk_state is not None:
1266       data["disk_state"] = \
1267         dict((key, self._ContainerToDicts(value))
1268              for (key, value) in disk_state.items())
1269
1270     return data
1271
1272   @classmethod
1273   def FromDict(cls, val):
1274     """Custom function for deserializing.
1275
1276     """
1277     obj = super(Node, cls).FromDict(val)
1278
1279     if obj.hv_state is not None:
1280       obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1281
1282     if obj.disk_state is not None:
1283       obj.disk_state = \
1284         dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1285              for (key, value) in obj.disk_state.items())
1286
1287     return obj
1288
1289
1290 class NodeGroup(TaggableObject):
1291   """Config object representing a node group."""
1292   __slots__ = [
1293     "name",
1294     "members",
1295     "ndparams",
1296     "diskparams",
1297     "ipolicy",
1298     "serial_no",
1299     "hv_state_static",
1300     "disk_state_static",
1301     "alloc_policy",
1302     ] + _TIMESTAMPS + _UUID
1303
1304   def ToDict(self):
1305     """Custom function for nodegroup.
1306
1307     This discards the members object, which gets recalculated and is only kept
1308     in memory.
1309
1310     """
1311     mydict = super(NodeGroup, self).ToDict()
1312     del mydict["members"]
1313     return mydict
1314
1315   @classmethod
1316   def FromDict(cls, val):
1317     """Custom function for nodegroup.
1318
1319     The members slot is initialized to an empty list, upon deserialization.
1320
1321     """
1322     obj = super(NodeGroup, cls).FromDict(val)
1323     obj.members = []
1324     return obj
1325
1326   def UpgradeConfig(self):
1327     """Fill defaults for missing configuration values.
1328
1329     """
1330     if self.ndparams is None:
1331       self.ndparams = {}
1332
1333     if self.serial_no is None:
1334       self.serial_no = 1
1335
1336     if self.alloc_policy is None:
1337       self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1338
1339     # We only update mtime, and not ctime, since we would not be able
1340     # to provide a correct value for creation time.
1341     if self.mtime is None:
1342       self.mtime = time.time()
1343
1344     self.diskparams = UpgradeDiskParams(self.diskparams)
1345     if self.ipolicy is None:
1346       self.ipolicy = MakeEmptyIPolicy()
1347
1348   def FillND(self, node):
1349     """Return filled out ndparams for L{objects.Node}
1350
1351     @type node: L{objects.Node}
1352     @param node: A Node object to fill
1353     @return a copy of the node's ndparams with defaults filled
1354
1355     """
1356     return self.SimpleFillND(node.ndparams)
1357
1358   def SimpleFillND(self, ndparams):
1359     """Fill a given ndparams dict with defaults.
1360
1361     @type ndparams: dict
1362     @param ndparams: the dict to fill
1363     @rtype: dict
1364     @return: a copy of the passed in ndparams with missing keys filled
1365         from the node group defaults
1366
1367     """
1368     return FillDict(self.ndparams, ndparams)
1369
1370
1371 class Cluster(TaggableObject):
1372   """Config object representing the cluster."""
1373   __slots__ = [
1374     "serial_no",
1375     "rsahostkeypub",
1376     "highest_used_port",
1377     "tcpudp_port_pool",
1378     "mac_prefix",
1379     "volume_group_name",
1380     "reserved_lvs",
1381     "drbd_usermode_helper",
1382     "default_bridge",
1383     "default_hypervisor",
1384     "master_node",
1385     "master_ip",
1386     "master_netdev",
1387     "master_netmask",
1388     "use_external_mip_script",
1389     "cluster_name",
1390     "file_storage_dir",
1391     "shared_file_storage_dir",
1392     "enabled_hypervisors",
1393     "hvparams",
1394     "ipolicy",
1395     "os_hvp",
1396     "beparams",
1397     "osparams",
1398     "nicparams",
1399     "ndparams",
1400     "diskparams",
1401     "candidate_pool_size",
1402     "modify_etc_hosts",
1403     "modify_ssh_setup",
1404     "maintain_node_health",
1405     "uid_pool",
1406     "default_iallocator",
1407     "hidden_os",
1408     "blacklisted_os",
1409     "primary_ip_family",
1410     "prealloc_wipe_disks",
1411     "hv_state_static",
1412     "disk_state_static",
1413     ] + _TIMESTAMPS + _UUID
1414
1415   def UpgradeConfig(self):
1416     """Fill defaults for missing configuration values.
1417
1418     """
1419     # pylint: disable=E0203
1420     # because these are "defined" via slots, not manually
1421     if self.hvparams is None:
1422       self.hvparams = constants.HVC_DEFAULTS
1423     else:
1424       for hypervisor in self.hvparams:
1425         self.hvparams[hypervisor] = FillDict(
1426             constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1427
1428     if self.os_hvp is None:
1429       self.os_hvp = {}
1430
1431     # osparams added before 2.2
1432     if self.osparams is None:
1433       self.osparams = {}
1434
1435     if self.ndparams is None:
1436       self.ndparams = constants.NDC_DEFAULTS
1437
1438     self.beparams = UpgradeGroupedParams(self.beparams,
1439                                          constants.BEC_DEFAULTS)
1440     for beparams_group in self.beparams:
1441       UpgradeBeParams(self.beparams[beparams_group])
1442
1443     migrate_default_bridge = not self.nicparams
1444     self.nicparams = UpgradeGroupedParams(self.nicparams,
1445                                           constants.NICC_DEFAULTS)
1446     if migrate_default_bridge:
1447       self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1448         self.default_bridge
1449
1450     if self.modify_etc_hosts is None:
1451       self.modify_etc_hosts = True
1452
1453     if self.modify_ssh_setup is None:
1454       self.modify_ssh_setup = True
1455
1456     # default_bridge is no longer used in 2.1. The slot is left there to
1457     # support auto-upgrading. It can be removed once we decide to deprecate
1458     # upgrading straight from 2.0.
1459     if self.default_bridge is not None:
1460       self.default_bridge = None
1461
1462     # default_hypervisor is just the first enabled one in 2.1. This slot and
1463     # code can be removed once upgrading straight from 2.0 is deprecated.
1464     if self.default_hypervisor is not None:
1465       self.enabled_hypervisors = ([self.default_hypervisor] +
1466         [hvname for hvname in self.enabled_hypervisors
1467          if hvname != self.default_hypervisor])
1468       self.default_hypervisor = None
1469
1470     # maintain_node_health added after 2.1.1
1471     if self.maintain_node_health is None:
1472       self.maintain_node_health = False
1473
1474     if self.uid_pool is None:
1475       self.uid_pool = []
1476
1477     if self.default_iallocator is None:
1478       self.default_iallocator = ""
1479
1480     # reserved_lvs added before 2.2
1481     if self.reserved_lvs is None:
1482       self.reserved_lvs = []
1483
1484     # hidden and blacklisted operating systems added before 2.2.1
1485     if self.hidden_os is None:
1486       self.hidden_os = []
1487
1488     if self.blacklisted_os is None:
1489       self.blacklisted_os = []
1490
1491     # primary_ip_family added before 2.3
1492     if self.primary_ip_family is None:
1493       self.primary_ip_family = AF_INET
1494
1495     if self.master_netmask is None:
1496       ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1497       self.master_netmask = ipcls.iplen
1498
1499     if self.prealloc_wipe_disks is None:
1500       self.prealloc_wipe_disks = False
1501
1502     # shared_file_storage_dir added before 2.5
1503     if self.shared_file_storage_dir is None:
1504       self.shared_file_storage_dir = ""
1505
1506     if self.use_external_mip_script is None:
1507       self.use_external_mip_script = False
1508
1509     self.diskparams = UpgradeDiskParams(self.diskparams)
1510
1511     # instance policy added before 2.6
1512     if self.ipolicy is None:
1513       self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1514     else:
1515       # we can either make sure to upgrade the ipolicy always, or only
1516       # do it in some corner cases (e.g. missing keys); note that this
1517       # will break any removal of keys from the ipolicy dict
1518       self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1519
1520   @property
1521   def primary_hypervisor(self):
1522     """The first hypervisor is the primary.
1523
1524     Useful, for example, for L{Node}'s hv/disk state.
1525
1526     """
1527     return self.enabled_hypervisors[0]
1528
1529   def ToDict(self):
1530     """Custom function for cluster.
1531
1532     """
1533     mydict = super(Cluster, self).ToDict()
1534     mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1535     return mydict
1536
1537   @classmethod
1538   def FromDict(cls, val):
1539     """Custom function for cluster.
1540
1541     """
1542     obj = super(Cluster, cls).FromDict(val)
1543     if not isinstance(obj.tcpudp_port_pool, set):
1544       obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1545     return obj
1546
1547   def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1548     """Get the default hypervisor parameters for the cluster.
1549
1550     @param hypervisor: the hypervisor name
1551     @param os_name: if specified, we'll also update the defaults for this OS
1552     @param skip_keys: if passed, list of keys not to use
1553     @return: the defaults dict
1554
1555     """
1556     if skip_keys is None:
1557       skip_keys = []
1558
1559     fill_stack = [self.hvparams.get(hypervisor, {})]
1560     if os_name is not None:
1561       os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1562       fill_stack.append(os_hvp)
1563
1564     ret_dict = {}
1565     for o_dict in fill_stack:
1566       ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1567
1568     return ret_dict
1569
1570   def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1571     """Fill a given hvparams dict with cluster defaults.
1572
1573     @type hv_name: string
1574     @param hv_name: the hypervisor to use
1575     @type os_name: string
1576     @param os_name: the OS to use for overriding the hypervisor defaults
1577     @type skip_globals: boolean
1578     @param skip_globals: if True, the global hypervisor parameters will
1579         not be filled
1580     @rtype: dict
1581     @return: a copy of the given hvparams with missing keys filled from
1582         the cluster defaults
1583
1584     """
1585     if skip_globals:
1586       skip_keys = constants.HVC_GLOBALS
1587     else:
1588       skip_keys = []
1589
1590     def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1591     return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1592
1593   def FillHV(self, instance, skip_globals=False):
1594     """Fill an instance's hvparams dict with cluster defaults.
1595
1596     @type instance: L{objects.Instance}
1597     @param instance: the instance parameter to fill
1598     @type skip_globals: boolean
1599     @param skip_globals: if True, the global hypervisor parameters will
1600         not be filled
1601     @rtype: dict
1602     @return: a copy of the instance's hvparams with missing keys filled from
1603         the cluster defaults
1604
1605     """
1606     return self.SimpleFillHV(instance.hypervisor, instance.os,
1607                              instance.hvparams, skip_globals)
1608
1609   def SimpleFillBE(self, beparams):
1610     """Fill a given beparams dict with cluster defaults.
1611
1612     @type beparams: dict
1613     @param beparams: the dict to fill
1614     @rtype: dict
1615     @return: a copy of the passed in beparams with missing keys filled
1616         from the cluster defaults
1617
1618     """
1619     return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1620
1621   def FillBE(self, instance):
1622     """Fill an instance's beparams dict with cluster defaults.
1623
1624     @type instance: L{objects.Instance}
1625     @param instance: the instance parameter to fill
1626     @rtype: dict
1627     @return: a copy of the instance's beparams with missing keys filled from
1628         the cluster defaults
1629
1630     """
1631     return self.SimpleFillBE(instance.beparams)
1632
1633   def SimpleFillNIC(self, nicparams):
1634     """Fill a given nicparams dict with cluster defaults.
1635
1636     @type nicparams: dict
1637     @param nicparams: the dict to fill
1638     @rtype: dict
1639     @return: a copy of the passed in nicparams with missing keys filled
1640         from the cluster defaults
1641
1642     """
1643     return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1644
1645   def SimpleFillOS(self, os_name, os_params):
1646     """Fill an instance's osparams dict with cluster defaults.
1647
1648     @type os_name: string
1649     @param os_name: the OS name to use
1650     @type os_params: dict
1651     @param os_params: the dict to fill with default values
1652     @rtype: dict
1653     @return: a copy of the instance's osparams with missing keys filled from
1654         the cluster defaults
1655
1656     """
1657     name_only = os_name.split("+", 1)[0]
1658     # base OS
1659     result = self.osparams.get(name_only, {})
1660     # OS with variant
1661     result = FillDict(result, self.osparams.get(os_name, {}))
1662     # specified params
1663     return FillDict(result, os_params)
1664
1665   @staticmethod
1666   def SimpleFillHvState(hv_state):
1667     """Fill an hv_state sub dict with cluster defaults.
1668
1669     """
1670     return FillDict(constants.HVST_DEFAULTS, hv_state)
1671
1672   @staticmethod
1673   def SimpleFillDiskState(disk_state):
1674     """Fill an disk_state sub dict with cluster defaults.
1675
1676     """
1677     return FillDict(constants.DS_DEFAULTS, disk_state)
1678
1679   def FillND(self, node, nodegroup):
1680     """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1681
1682     @type node: L{objects.Node}
1683     @param node: A Node object to fill
1684     @type nodegroup: L{objects.NodeGroup}
1685     @param nodegroup: A Node object to fill
1686     @return a copy of the node's ndparams with defaults filled
1687
1688     """
1689     return self.SimpleFillND(nodegroup.FillND(node))
1690
1691   def SimpleFillND(self, ndparams):
1692     """Fill a given ndparams dict with defaults.
1693
1694     @type ndparams: dict
1695     @param ndparams: the dict to fill
1696     @rtype: dict
1697     @return: a copy of the passed in ndparams with missing keys filled
1698         from the cluster defaults
1699
1700     """
1701     return FillDict(self.ndparams, ndparams)
1702
1703   def SimpleFillIPolicy(self, ipolicy):
1704     """ Fill instance policy dict with defaults.
1705
1706     @type ipolicy: dict
1707     @param ipolicy: the dict to fill
1708     @rtype: dict
1709     @return: a copy of passed ipolicy with missing keys filled from
1710       the cluster defaults
1711
1712     """
1713     return FillIPolicy(self.ipolicy, ipolicy)
1714
1715
1716 class BlockDevStatus(ConfigObject):
1717   """Config object representing the status of a block device."""
1718   __slots__ = [
1719     "dev_path",
1720     "major",
1721     "minor",
1722     "sync_percent",
1723     "estimated_time",
1724     "is_degraded",
1725     "ldisk_status",
1726     ]
1727
1728
1729 class ImportExportStatus(ConfigObject):
1730   """Config object representing the status of an import or export."""
1731   __slots__ = [
1732     "recent_output",
1733     "listen_port",
1734     "connected",
1735     "progress_mbytes",
1736     "progress_throughput",
1737     "progress_eta",
1738     "progress_percent",
1739     "exit_status",
1740     "error_message",
1741     ] + _TIMESTAMPS
1742
1743
1744 class ImportExportOptions(ConfigObject):
1745   """Options for import/export daemon
1746
1747   @ivar key_name: X509 key name (None for cluster certificate)
1748   @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1749   @ivar compress: Compression method (one of L{constants.IEC_ALL})
1750   @ivar magic: Used to ensure the connection goes to the right disk
1751   @ivar ipv6: Whether to use IPv6
1752   @ivar connect_timeout: Number of seconds for establishing connection
1753
1754   """
1755   __slots__ = [
1756     "key_name",
1757     "ca_pem",
1758     "compress",
1759     "magic",
1760     "ipv6",
1761     "connect_timeout",
1762     ]
1763
1764
1765 class ConfdRequest(ConfigObject):
1766   """Object holding a confd request.
1767
1768   @ivar protocol: confd protocol version
1769   @ivar type: confd query type
1770   @ivar query: query request
1771   @ivar rsalt: requested reply salt
1772
1773   """
1774   __slots__ = [
1775     "protocol",
1776     "type",
1777     "query",
1778     "rsalt",
1779     ]
1780
1781
1782 class ConfdReply(ConfigObject):
1783   """Object holding a confd reply.
1784
1785   @ivar protocol: confd protocol version
1786   @ivar status: reply status code (ok, error)
1787   @ivar answer: confd query reply
1788   @ivar serial: configuration serial number
1789
1790   """
1791   __slots__ = [
1792     "protocol",
1793     "status",
1794     "answer",
1795     "serial",
1796     ]
1797
1798
1799 class QueryFieldDefinition(ConfigObject):
1800   """Object holding a query field definition.
1801
1802   @ivar name: Field name
1803   @ivar title: Human-readable title
1804   @ivar kind: Field type
1805   @ivar doc: Human-readable description
1806
1807   """
1808   __slots__ = [
1809     "name",
1810     "title",
1811     "kind",
1812     "doc",
1813     ]
1814
1815
1816 class _QueryResponseBase(ConfigObject):
1817   __slots__ = [
1818     "fields",
1819     ]
1820
1821   def ToDict(self):
1822     """Custom function for serializing.
1823
1824     """
1825     mydict = super(_QueryResponseBase, self).ToDict()
1826     mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1827     return mydict
1828
1829   @classmethod
1830   def FromDict(cls, val):
1831     """Custom function for de-serializing.
1832
1833     """
1834     obj = super(_QueryResponseBase, cls).FromDict(val)
1835     obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1836     return obj
1837
1838
1839 class QueryRequest(ConfigObject):
1840   """Object holding a query request.
1841
1842   """
1843   __slots__ = [
1844     "what",
1845     "fields",
1846     "qfilter",
1847     ]
1848
1849
1850 class QueryResponse(_QueryResponseBase):
1851   """Object holding the response to a query.
1852
1853   @ivar fields: List of L{QueryFieldDefinition} objects
1854   @ivar data: Requested data
1855
1856   """
1857   __slots__ = [
1858     "data",
1859     ]
1860
1861
1862 class QueryFieldsRequest(ConfigObject):
1863   """Object holding a request for querying available fields.
1864
1865   """
1866   __slots__ = [
1867     "what",
1868     "fields",
1869     ]
1870
1871
1872 class QueryFieldsResponse(_QueryResponseBase):
1873   """Object holding the response to a query for fields.
1874
1875   @ivar fields: List of L{QueryFieldDefinition} objects
1876
1877   """
1878   __slots__ = [
1879     ]
1880
1881
1882 class MigrationStatus(ConfigObject):
1883   """Object holding the status of a migration.
1884
1885   """
1886   __slots__ = [
1887     "status",
1888     "transferred_ram",
1889     "total_ram",
1890     ]
1891
1892
1893 class InstanceConsole(ConfigObject):
1894   """Object describing how to access the console of an instance.
1895
1896   """
1897   __slots__ = [
1898     "instance",
1899     "kind",
1900     "message",
1901     "host",
1902     "port",
1903     "user",
1904     "command",
1905     "display",
1906     ]
1907
1908   def Validate(self):
1909     """Validates contents of this object.
1910
1911     """
1912     assert self.kind in constants.CONS_ALL, "Unknown console type"
1913     assert self.instance, "Missing instance name"
1914     assert self.message or self.kind in [constants.CONS_SSH,
1915                                          constants.CONS_SPICE,
1916                                          constants.CONS_VNC]
1917     assert self.host or self.kind == constants.CONS_MESSAGE
1918     assert self.port or self.kind in [constants.CONS_MESSAGE,
1919                                       constants.CONS_SSH]
1920     assert self.user or self.kind in [constants.CONS_MESSAGE,
1921                                       constants.CONS_SPICE,
1922                                       constants.CONS_VNC]
1923     assert self.command or self.kind in [constants.CONS_MESSAGE,
1924                                          constants.CONS_SPICE,
1925                                          constants.CONS_VNC]
1926     assert self.display or self.kind in [constants.CONS_MESSAGE,
1927                                          constants.CONS_SPICE,
1928                                          constants.CONS_SSH]
1929     return True
1930
1931
1932 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1933   """Simple wrapper over ConfigParse that allows serialization.
1934
1935   This class is basically ConfigParser.SafeConfigParser with two
1936   additional methods that allow it to serialize/unserialize to/from a
1937   buffer.
1938
1939   """
1940   def Dumps(self):
1941     """Dump this instance and return the string representation."""
1942     buf = StringIO()
1943     self.write(buf)
1944     return buf.getvalue()
1945
1946   @classmethod
1947   def Loads(cls, data):
1948     """Load data from a string."""
1949     buf = StringIO(data)
1950     cfp = cls()
1951     cfp.readfp(buf)
1952     return cfp