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