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