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