4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
22 """Transportable objects for Ganeti.
24 This module provides small, mostly data-only objects which are safe to
25 pass to and from external parties.
29 # pylint: disable-msg=E0203,W0201
31 # E0203: Access to member %r before its definition, since we use
32 # objects.py which doesn't explicitely initialise its members
34 # W0201: Attribute '%s' defined outside __init__
39 from cStringIO import StringIO
41 from ganeti import errors
42 from ganeti import constants
45 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
46 "OS", "Node", "Cluster", "FillDict"]
48 _TIMESTAMPS = ["ctime", "mtime"]
52 def FillDict(defaults_dict, custom_dict, skip_keys=None):
53 """Basic function to apply settings on top a default dict.
55 @type defaults_dict: dict
56 @param defaults_dict: dictionary holding the default values
57 @type custom_dict: dict
58 @param custom_dict: dictionary holding customized value
60 @param skip_keys: which keys not to fill
62 @return: dict with the 'full' values
65 ret_dict = copy.deepcopy(defaults_dict)
66 ret_dict.update(custom_dict)
76 def UpgradeGroupedParams(target, defaults):
77 """Update all groups for the target parameter.
79 @type target: dict of dicts
80 @param target: {group: {parameter: value}}
82 @param defaults: default parameter values
86 target = {constants.PP_DEFAULT: defaults}
89 target[group] = FillDict(defaults, target[group])
93 class ConfigObject(object):
94 """A generic config object.
96 It has the following properties:
98 - provides somewhat safe recursive unpickling and pickling for its classes
99 - unset attributes which are defined in slots are always returned
100 as None instead of raising an error
102 Classes derived from this must always declare __slots__ (we use many
103 config objects and the memory reduction is useful)
108 def __init__(self, **kwargs):
109 for k, v in kwargs.iteritems():
112 def __getattr__(self, name):
113 if name not in self._all_slots():
114 raise AttributeError("Invalid object attribute %s.%s" %
115 (type(self).__name__, name))
118 def __setstate__(self, state):
119 slots = self._all_slots()
122 setattr(self, name, state[name])
126 """Compute the list of all declared slots for a class.
130 for parent in cls.__mro__:
131 slots.extend(getattr(parent, "__slots__", []))
135 """Convert to a dict holding only standard python types.
137 The generic routine just dumps all of this object's attributes in
138 a dict. It does not work if the class has children who are
139 ConfigObjects themselves (e.g. the nics list in an Instance), in
140 which case the object should subclass the function in order to
141 make sure all objects returned are only standard python types.
145 for name in self._all_slots():
146 value = getattr(self, name, None)
147 if value is not None:
151 __getstate__ = ToDict
154 def FromDict(cls, val):
155 """Create an object from a dictionary.
157 This generic routine takes a dict, instantiates a new instance of
158 the given class, and sets attributes based on the dict content.
160 As for `ToDict`, this does not work if the class has children
161 who are ConfigObjects themselves (e.g. the nics list in an
162 Instance), in which case the object should subclass the function
163 and alter the objects.
166 if not isinstance(val, dict):
167 raise errors.ConfigurationError("Invalid object passed to FromDict:"
168 " expected dict, got %s" % type(val))
169 val_str = dict([(str(k), v) for k, v in val.iteritems()])
170 obj = cls(**val_str) # pylint: disable-msg=W0142
174 def _ContainerToDicts(container):
175 """Convert the elements of a container to standard python types.
177 This method converts a container with elements derived from
178 ConfigData to standard python types. If the container is a dict,
179 we don't touch the keys, only the values.
182 if isinstance(container, dict):
183 ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
184 elif isinstance(container, (list, tuple, set, frozenset)):
185 ret = [elem.ToDict() for elem in container]
187 raise TypeError("Invalid type %s passed to _ContainerToDicts" %
192 def _ContainerFromDicts(source, c_type, e_type):
193 """Convert a container from standard python types.
195 This method converts a container with standard python types to
196 ConfigData objects. If the container is a dict, we don't touch the
197 keys, only the values.
200 if not isinstance(c_type, type):
201 raise TypeError("Container type %s passed to _ContainerFromDicts is"
202 " not a type" % type(c_type))
204 ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
205 elif c_type in (list, tuple, set, frozenset):
206 ret = c_type([e_type.FromDict(elem) for elem in source])
208 raise TypeError("Invalid container type %s passed to"
209 " _ContainerFromDicts" % c_type)
213 """Makes a deep copy of the current object and its children.
216 dict_form = self.ToDict()
217 clone_obj = self.__class__.FromDict(dict_form)
221 """Implement __repr__ for ConfigObjects."""
222 return repr(self.ToDict())
224 def UpgradeConfig(self):
225 """Fill defaults for missing configuration values.
227 This method will be called at configuration load time, and its
228 implementation will be object dependent.
234 class TaggableObject(ConfigObject):
235 """An generic class supporting tags.
239 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
242 def ValidateTag(cls, tag):
243 """Check if a tag is valid.
245 If the tag is invalid, an errors.TagError will be raised. The
246 function has no return value.
249 if not isinstance(tag, basestring):
250 raise errors.TagError("Invalid tag type (not a string)")
251 if len(tag) > constants.MAX_TAG_LEN:
252 raise errors.TagError("Tag too long (>%d characters)" %
253 constants.MAX_TAG_LEN)
255 raise errors.TagError("Tags cannot be empty")
256 if not cls.VALID_TAG_RE.match(tag):
257 raise errors.TagError("Tag contains invalid characters")
260 """Return the tags list.
263 tags = getattr(self, "tags", None)
265 tags = self.tags = set()
268 def AddTag(self, tag):
272 self.ValidateTag(tag)
273 tags = self.GetTags()
274 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
275 raise errors.TagError("Too many tags")
276 self.GetTags().add(tag)
278 def RemoveTag(self, tag):
282 self.ValidateTag(tag)
283 tags = self.GetTags()
287 raise errors.TagError("Tag not found")
290 """Taggable-object-specific conversion to standard python types.
292 This replaces the tags set with a list.
295 bo = super(TaggableObject, self).ToDict()
297 tags = bo.get("tags", None)
298 if isinstance(tags, set):
299 bo["tags"] = list(tags)
303 def FromDict(cls, val):
304 """Custom function for instances.
307 obj = super(TaggableObject, cls).FromDict(val)
308 if hasattr(obj, "tags") and isinstance(obj.tags, list):
309 obj.tags = set(obj.tags)
313 class ConfigData(ConfigObject):
314 """Top-level config object."""
315 __slots__ = (["version", "cluster", "nodes", "instances", "serial_no"] +
319 """Custom function for top-level config data.
321 This just replaces the list of instances, nodes and the cluster
322 with standard python types.
325 mydict = super(ConfigData, self).ToDict()
326 mydict["cluster"] = mydict["cluster"].ToDict()
327 for key in "nodes", "instances":
328 mydict[key] = self._ContainerToDicts(mydict[key])
333 def FromDict(cls, val):
334 """Custom function for top-level config data
337 obj = super(ConfigData, cls).FromDict(val)
338 obj.cluster = Cluster.FromDict(obj.cluster)
339 obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
340 obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
343 def UpgradeConfig(self):
344 """Fill defaults for missing configuration values.
347 self.cluster.UpgradeConfig()
348 for node in self.nodes.values():
350 for instance in self.instances.values():
351 instance.UpgradeConfig()
354 class NIC(ConfigObject):
355 """Config object representing a network card."""
356 __slots__ = ["mac", "ip", "bridge", "nicparams"]
359 def CheckParameterSyntax(cls, nicparams):
360 """Check the given parameters for validity.
362 @type nicparams: dict
363 @param nicparams: dictionary with parameter names/value
364 @raise errors.ConfigurationError: when a parameter is not valid
367 if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
368 err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
369 raise errors.ConfigurationError(err)
371 if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
372 not nicparams[constants.NIC_LINK]):
373 err = "Missing bridged nic link"
374 raise errors.ConfigurationError(err)
376 def UpgradeConfig(self):
377 """Fill defaults for missing configuration values.
380 if self.nicparams is None:
382 if self.bridge is not None:
383 self.nicparams[constants.NIC_MODE] = constants.NIC_MODE_BRIDGED
384 self.nicparams[constants.NIC_LINK] = self.bridge
385 # bridge is no longer used it 2.1. The slot is left there to support
386 # upgrading, but will be removed in 2.2
387 if self.bridge is not None:
391 class Disk(ConfigObject):
392 """Config object representing a block device."""
393 __slots__ = ["dev_type", "logical_id", "physical_id",
394 "children", "iv_name", "size", "mode"]
396 def CreateOnSecondary(self):
397 """Test if this device needs to be created on a secondary node."""
398 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
400 def AssembleOnSecondary(self):
401 """Test if this device needs to be assembled on a secondary node."""
402 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
404 def OpenOnSecondary(self):
405 """Test if this device needs to be opened on a secondary node."""
406 return self.dev_type in (constants.LD_LV,)
408 def StaticDevPath(self):
409 """Return the device path if this device type has a static one.
411 Some devices (LVM for example) live always at the same /dev/ path,
412 irrespective of their status. For such devices, we return this
413 path, for others we return None.
415 @warning: The path returned is not a normalized pathname; callers
416 should check that it is a valid path.
419 if self.dev_type == constants.LD_LV:
420 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
423 def ChildrenNeeded(self):
424 """Compute the needed number of children for activation.
426 This method will return either -1 (all children) or a positive
427 number denoting the minimum number of children needed for
428 activation (only mirrored devices will usually return >=0).
430 Currently, only DRBD8 supports diskless activation (therefore we
431 return 0), for all other we keep the previous semantics and return
435 if self.dev_type == constants.LD_DRBD8:
439 def GetNodes(self, node):
440 """This function returns the nodes this device lives on.
442 Given the node on which the parent of the device lives on (or, in
443 case of a top-level device, the primary node of the devices'
444 instance), this function will return a list of nodes on which this
445 devices needs to (or can) be assembled.
448 if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
450 elif self.dev_type in constants.LDS_DRBD:
451 result = [self.logical_id[0], self.logical_id[1]]
452 if node not in result:
453 raise errors.ConfigurationError("DRBD device passed unknown node")
455 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
458 def ComputeNodeTree(self, parent_node):
459 """Compute the node/disk tree for this disk and its children.
461 This method, given the node on which the parent disk lives, will
462 return the list of all (node, disk) pairs which describe the disk
463 tree in the most compact way. For example, a drbd/lvm stack
464 will be returned as (primary_node, drbd) and (secondary_node, drbd)
465 which represents all the top-level devices on the nodes.
468 my_nodes = self.GetNodes(parent_node)
469 result = [(node, self) for node in my_nodes]
470 if not self.children:
473 for node in my_nodes:
474 for child in self.children:
475 child_result = child.ComputeNodeTree(node)
476 if len(child_result) == 1:
477 # child (and all its descendants) is simple, doesn't split
478 # over multiple hosts, so we don't need to describe it, our
479 # own entry for this node describes it completely
482 # check if child nodes differ from my nodes; note that
483 # subdisk can differ from the child itself, and be instead
484 # one of its descendants
485 for subnode, subdisk in child_result:
486 if subnode not in my_nodes:
487 result.append((subnode, subdisk))
488 # otherwise child is under our own node, so we ignore this
489 # entry (but probably the other results in the list will
493 def RecordGrow(self, amount):
494 """Update the size of this disk after growth.
496 This method recurses over the disks's children and updates their
497 size correspondigly. The method needs to be kept in sync with the
498 actual algorithms from bdev.
501 if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
503 elif self.dev_type == constants.LD_DRBD8:
505 self.children[0].RecordGrow(amount)
508 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
509 " disk type %s" % self.dev_type)
512 """Sets recursively the size to zero for the disk and its children.
516 for child in self.children:
520 def SetPhysicalID(self, target_node, nodes_ip):
521 """Convert the logical ID to the physical ID.
523 This is used only for drbd, which needs ip/port configuration.
525 The routine descends down and updates its children also, because
526 this helps when the only the top device is passed to the remote
530 - target_node: the node we wish to configure for
531 - nodes_ip: a mapping of node name to ip
533 The target_node must exist in in nodes_ip, and must be one of the
534 nodes in the logical ID for each of the DRBD devices encountered
539 for child in self.children:
540 child.SetPhysicalID(target_node, nodes_ip)
542 if self.logical_id is None and self.physical_id is not None:
544 if self.dev_type in constants.LDS_DRBD:
545 pnode, snode, port, pminor, sminor, secret = self.logical_id
546 if target_node not in (pnode, snode):
547 raise errors.ConfigurationError("DRBD device not knowing node %s" %
549 pnode_ip = nodes_ip.get(pnode, None)
550 snode_ip = nodes_ip.get(snode, None)
551 if pnode_ip is None or snode_ip is None:
552 raise errors.ConfigurationError("Can't find primary or secondary node"
553 " for %s" % str(self))
554 p_data = (pnode_ip, port)
555 s_data = (snode_ip, port)
556 if pnode == target_node:
557 self.physical_id = p_data + s_data + (pminor, secret)
558 else: # it must be secondary, we tested above
559 self.physical_id = s_data + p_data + (sminor, secret)
561 self.physical_id = self.logical_id
565 """Disk-specific conversion to standard python types.
567 This replaces the children lists of objects with lists of
568 standard python types.
571 bo = super(Disk, self).ToDict()
573 for attr in ("children",):
574 alist = bo.get(attr, None)
576 bo[attr] = self._ContainerToDicts(alist)
580 def FromDict(cls, val):
581 """Custom function for Disks
584 obj = super(Disk, cls).FromDict(val)
586 obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
587 if obj.logical_id and isinstance(obj.logical_id, list):
588 obj.logical_id = tuple(obj.logical_id)
589 if obj.physical_id and isinstance(obj.physical_id, list):
590 obj.physical_id = tuple(obj.physical_id)
591 if obj.dev_type in constants.LDS_DRBD:
592 # we need a tuple of length six here
593 if len(obj.logical_id) < 6:
594 obj.logical_id += (None,) * (6 - len(obj.logical_id))
598 """Custom str() formatter for disks.
601 if self.dev_type == constants.LD_LV:
602 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
603 elif self.dev_type in constants.LDS_DRBD:
604 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
606 if self.physical_id is None:
609 phy = ("configured as %s:%s %s:%s" %
610 (self.physical_id[0], self.physical_id[1],
611 self.physical_id[2], self.physical_id[3]))
613 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
614 (node_a, minor_a, node_b, minor_b, port, phy))
615 if self.children and self.children.count(None) == 0:
616 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
618 val += "no local storage"
620 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
621 (self.dev_type, self.logical_id, self.physical_id, self.children))
622 if self.iv_name is None:
623 val += ", not visible"
625 val += ", visible as /dev/%s" % self.iv_name
626 if isinstance(self.size, int):
627 val += ", size=%dm)>" % self.size
629 val += ", size='%s')>" % (self.size,)
633 """Checks that this disk is correctly configured.
637 if self.mode not in constants.DISK_ACCESS_SET:
638 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
641 def UpgradeConfig(self):
642 """Fill defaults for missing configuration values.
646 for child in self.children:
647 child.UpgradeConfig()
648 # add here config upgrade for this disk
651 class Instance(TaggableObject):
652 """Config object representing an instance."""
666 ] + _TIMESTAMPS + _UUID
668 def _ComputeSecondaryNodes(self):
669 """Compute the list of secondary nodes.
671 This is a simple wrapper over _ComputeAllNodes.
674 all_nodes = set(self._ComputeAllNodes())
675 all_nodes.discard(self.primary_node)
676 return tuple(all_nodes)
678 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
679 "List of secondary nodes")
681 def _ComputeAllNodes(self):
682 """Compute the list of all nodes.
684 Since the data is already there (in the drbd disks), keeping it as
685 a separate normal attribute is redundant and if not properly
686 synchronised can cause problems. Thus it's better to compute it
690 def _Helper(nodes, device):
691 """Recursively computes nodes given a top device."""
692 if device.dev_type in constants.LDS_DRBD:
693 nodea, nodeb = device.logical_id[:2]
697 for child in device.children:
698 _Helper(nodes, child)
701 all_nodes.add(self.primary_node)
702 for device in self.disks:
703 _Helper(all_nodes, device)
704 return tuple(all_nodes)
706 all_nodes = property(_ComputeAllNodes, None, None,
707 "List of all nodes of the instance")
709 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
710 """Provide a mapping of nodes to LVs this instance owns.
712 This function figures out what logical volumes should belong on
713 which nodes, recursing through a device tree.
715 @param lvmap: optional dictionary to receive the
716 'node' : ['lv', ...] data.
718 @return: None if lvmap arg is given, otherwise, a dictionary
719 of the form { 'nodename' : ['volume1', 'volume2', ...], ... }
723 node = self.primary_node
726 lvmap = { node : [] }
729 if not node in lvmap:
737 if dev.dev_type == constants.LD_LV:
738 lvmap[node].append(dev.logical_id[1])
740 elif dev.dev_type in constants.LDS_DRBD:
742 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
743 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
746 self.MapLVsByNode(lvmap, dev.children, node)
750 def FindDisk(self, idx):
751 """Find a disk given having a specified index.
753 This is just a wrapper that does validation of the index.
756 @param idx: the disk index
758 @return: the corresponding disk
759 @raise errors.OpPrereqError: when the given index is not valid
764 return self.disks[idx]
765 except (TypeError, ValueError), err:
766 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
769 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
770 " 0 to %d" % (idx, len(self.disks)),
774 """Instance-specific conversion to standard python types.
776 This replaces the children lists of objects with lists of standard
780 bo = super(Instance, self).ToDict()
782 for attr in "nics", "disks":
783 alist = bo.get(attr, None)
785 nlist = self._ContainerToDicts(alist)
792 def FromDict(cls, val):
793 """Custom function for instances.
796 obj = super(Instance, cls).FromDict(val)
797 obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
798 obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
801 def UpgradeConfig(self):
802 """Fill defaults for missing configuration values.
805 for nic in self.nics:
807 for disk in self.disks:
810 for key in constants.HVC_GLOBALS:
812 del self.hvparams[key]
817 class OS(ConfigObject):
818 """Config object representing an operating system."""
827 "supported_variants",
831 class Node(TaggableObject):
832 """Config object representing a node."""
841 ] + _TIMESTAMPS + _UUID
844 class Cluster(TaggableObject):
845 """Config object representing the cluster."""
854 "default_hypervisor",
860 "enabled_hypervisors",
865 "candidate_pool_size",
868 "maintain_node_health",
870 ] + _TIMESTAMPS + _UUID
872 def UpgradeConfig(self):
873 """Fill defaults for missing configuration values.
876 # pylint: disable-msg=E0203
877 # because these are "defined" via slots, not manually
878 if self.hvparams is None:
879 self.hvparams = constants.HVC_DEFAULTS
881 for hypervisor in self.hvparams:
882 self.hvparams[hypervisor] = FillDict(
883 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
885 if self.os_hvp is None:
888 self.beparams = UpgradeGroupedParams(self.beparams,
889 constants.BEC_DEFAULTS)
890 migrate_default_bridge = not self.nicparams
891 self.nicparams = UpgradeGroupedParams(self.nicparams,
892 constants.NICC_DEFAULTS)
893 if migrate_default_bridge:
894 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
897 if self.modify_etc_hosts is None:
898 self.modify_etc_hosts = True
900 if self.modify_ssh_setup is None:
901 self.modify_ssh_setup = True
903 # default_bridge is no longer used it 2.1. The slot is left there to
904 # support auto-upgrading, but will be removed in 2.2
905 if self.default_bridge is not None:
906 self.default_bridge = None
908 # default_hypervisor is just the first enabled one in 2.1
909 if self.default_hypervisor is not None:
910 self.enabled_hypervisors = ([self.default_hypervisor] +
911 [hvname for hvname in self.enabled_hypervisors
912 if hvname != self.default_hypervisor])
913 self.default_hypervisor = None
915 # maintain_node_health added after 2.1.1
916 if self.maintain_node_health is None:
917 self.maintain_node_health = False
919 if self.uid_pool is None:
923 """Custom function for cluster.
926 mydict = super(Cluster, self).ToDict()
927 mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
931 def FromDict(cls, val):
932 """Custom function for cluster.
935 obj = super(Cluster, cls).FromDict(val)
936 if not isinstance(obj.tcpudp_port_pool, set):
937 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
940 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
941 """Get the default hypervisor parameters for the cluster.
943 @param hypervisor: the hypervisor name
944 @param os_name: if specified, we'll also update the defaults for this OS
945 @param skip_keys: if passed, list of keys not to use
946 @return: the defaults dict
949 if skip_keys is None:
952 fill_stack = [self.hvparams.get(hypervisor, {})]
953 if os_name is not None:
954 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
955 fill_stack.append(os_hvp)
958 for o_dict in fill_stack:
959 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
963 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
964 """Fill a given hvparams dict with cluster defaults.
966 @type hv_name: string
967 @param hv_name: the hypervisor to use
968 @type os_name: string
969 @param os_name: the OS to use for overriding the hypervisor defaults
970 @type skip_globals: boolean
971 @param skip_globals: if True, the global hypervisor parameters will
974 @return: a copy of the given hvparams with missing keys filled from
979 skip_keys = constants.HVC_GLOBALS
983 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
984 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
986 def FillHV(self, instance, skip_globals=False):
987 """Fill an instance's hvparams dict with cluster defaults.
989 @type instance: L{objects.Instance}
990 @param instance: the instance parameter to fill
991 @type skip_globals: boolean
992 @param skip_globals: if True, the global hypervisor parameters will
995 @return: a copy of the instance's hvparams with missing keys filled from
999 return self.SimpleFillHV(instance.hypervisor, instance.os,
1000 instance.hvparams, skip_globals)
1002 def SimpleFillBE(self, beparams):
1003 """Fill a given beparams dict with cluster defaults.
1006 @param beparam: the dict to fill
1008 @return: a copy of the passed in beparams with missing keys filled
1009 from the cluster defaults
1012 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1014 def FillBE(self, instance):
1015 """Fill an instance's beparams dict with cluster defaults.
1017 @type instance: L{objects.Instance}
1018 @param instance: the instance parameter to fill
1020 @return: a copy of the instance's beparams with missing keys filled from
1021 the cluster defaults
1024 return self.SimpleFillBE(instance.beparams)
1026 def SimpleFillNIC(self, nicparams):
1027 """Fill a given nicparams dict with cluster defaults.
1029 @type nicparam: dict
1030 @param nicparam: the dict to fill
1032 @return: a copy of the passed in nicparams with missing keys filled
1033 from the cluster defaults
1036 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1039 class BlockDevStatus(ConfigObject):
1040 """Config object representing the status of a block device."""
1052 class ImportExportStatus(ConfigObject):
1053 """Config object representing the status of an import or export."""
1059 "progress_throughput",
1067 class ImportExportOptions(ConfigObject):
1068 """Options for import/export daemon
1070 @ivar key_name: X509 key name (None for cluster certificate)
1071 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1072 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1073 @ivar magic: Used to ensure the connection goes to the right disk
1084 class ConfdRequest(ConfigObject):
1085 """Object holding a confd request.
1087 @ivar protocol: confd protocol version
1088 @ivar type: confd query type
1089 @ivar query: query request
1090 @ivar rsalt: requested reply salt
1101 class ConfdReply(ConfigObject):
1102 """Object holding a confd reply.
1104 @ivar protocol: confd protocol version
1105 @ivar status: reply status code (ok, error)
1106 @ivar answer: confd query reply
1107 @ivar serial: configuration serial number
1118 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1119 """Simple wrapper over ConfigParse that allows serialization.
1121 This class is basically ConfigParser.SafeConfigParser with two
1122 additional methods that allow it to serialize/unserialize to/from a
1127 """Dump this instance and return the string representation."""
1130 return buf.getvalue()
1133 def Loads(cls, data):
1134 """Load data from a string."""
1135 buf = StringIO(data)