4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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__
40 from cStringIO import StringIO
42 from ganeti import errors
43 from ganeti import constants
45 from socket import AF_INET
48 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
49 "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
51 _TIMESTAMPS = ["ctime", "mtime"]
55 def FillDict(defaults_dict, custom_dict, skip_keys=None):
56 """Basic function to apply settings on top a default dict.
58 @type defaults_dict: dict
59 @param defaults_dict: dictionary holding the default values
60 @type custom_dict: dict
61 @param custom_dict: dictionary holding customized value
63 @param skip_keys: which keys not to fill
65 @return: dict with the 'full' values
68 ret_dict = copy.deepcopy(defaults_dict)
69 ret_dict.update(custom_dict)
79 def UpgradeGroupedParams(target, defaults):
80 """Update all groups for the target parameter.
82 @type target: dict of dicts
83 @param target: {group: {parameter: value}}
85 @param defaults: default parameter values
89 target = {constants.PP_DEFAULT: defaults}
92 target[group] = FillDict(defaults, target[group])
96 class ConfigObject(object):
97 """A generic config object.
99 It has the following properties:
101 - provides somewhat safe recursive unpickling and pickling for its classes
102 - unset attributes which are defined in slots are always returned
103 as None instead of raising an error
105 Classes derived from this must always declare __slots__ (we use many
106 config objects and the memory reduction is useful)
111 def __init__(self, **kwargs):
112 for k, v in kwargs.iteritems():
115 def __getattr__(self, name):
116 if name not in self._all_slots():
117 raise AttributeError("Invalid object attribute %s.%s" %
118 (type(self).__name__, name))
121 def __setstate__(self, state):
122 slots = self._all_slots()
125 setattr(self, name, state[name])
129 """Compute the list of all declared slots for a class.
133 for parent in cls.__mro__:
134 slots.extend(getattr(parent, "__slots__", []))
138 """Convert to a dict holding only standard python types.
140 The generic routine just dumps all of this object's attributes in
141 a dict. It does not work if the class has children who are
142 ConfigObjects themselves (e.g. the nics list in an Instance), in
143 which case the object should subclass the function in order to
144 make sure all objects returned are only standard python types.
148 for name in self._all_slots():
149 value = getattr(self, name, None)
150 if value is not None:
154 __getstate__ = ToDict
157 def FromDict(cls, val):
158 """Create an object from a dictionary.
160 This generic routine takes a dict, instantiates a new instance of
161 the given class, and sets attributes based on the dict content.
163 As for `ToDict`, this does not work if the class has children
164 who are ConfigObjects themselves (e.g. the nics list in an
165 Instance), in which case the object should subclass the function
166 and alter the objects.
169 if not isinstance(val, dict):
170 raise errors.ConfigurationError("Invalid object passed to FromDict:"
171 " expected dict, got %s" % type(val))
172 val_str = dict([(str(k), v) for k, v in val.iteritems()])
173 obj = cls(**val_str) # pylint: disable-msg=W0142
177 def _ContainerToDicts(container):
178 """Convert the elements of a container to standard python types.
180 This method converts a container with elements derived from
181 ConfigData to standard python types. If the container is a dict,
182 we don't touch the keys, only the values.
185 if isinstance(container, dict):
186 ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
187 elif isinstance(container, (list, tuple, set, frozenset)):
188 ret = [elem.ToDict() for elem in container]
190 raise TypeError("Invalid type %s passed to _ContainerToDicts" %
195 def _ContainerFromDicts(source, c_type, e_type):
196 """Convert a container from standard python types.
198 This method converts a container with standard python types to
199 ConfigData objects. If the container is a dict, we don't touch the
200 keys, only the values.
203 if not isinstance(c_type, type):
204 raise TypeError("Container type %s passed to _ContainerFromDicts is"
205 " not a type" % type(c_type))
209 ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
210 elif c_type in (list, tuple, set, frozenset):
211 ret = c_type([e_type.FromDict(elem) for elem in source])
213 raise TypeError("Invalid container type %s passed to"
214 " _ContainerFromDicts" % c_type)
218 """Makes a deep copy of the current object and its children.
221 dict_form = self.ToDict()
222 clone_obj = self.__class__.FromDict(dict_form)
226 """Implement __repr__ for ConfigObjects."""
227 return repr(self.ToDict())
229 def UpgradeConfig(self):
230 """Fill defaults for missing configuration values.
232 This method will be called at configuration load time, and its
233 implementation will be object dependent.
239 class TaggableObject(ConfigObject):
240 """An generic class supporting tags.
244 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
247 def ValidateTag(cls, tag):
248 """Check if a tag is valid.
250 If the tag is invalid, an errors.TagError will be raised. The
251 function has no return value.
254 if not isinstance(tag, basestring):
255 raise errors.TagError("Invalid tag type (not a string)")
256 if len(tag) > constants.MAX_TAG_LEN:
257 raise errors.TagError("Tag too long (>%d characters)" %
258 constants.MAX_TAG_LEN)
260 raise errors.TagError("Tags cannot be empty")
261 if not cls.VALID_TAG_RE.match(tag):
262 raise errors.TagError("Tag contains invalid characters")
265 """Return the tags list.
268 tags = getattr(self, "tags", None)
270 tags = self.tags = set()
273 def AddTag(self, tag):
277 self.ValidateTag(tag)
278 tags = self.GetTags()
279 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
280 raise errors.TagError("Too many tags")
281 self.GetTags().add(tag)
283 def RemoveTag(self, tag):
287 self.ValidateTag(tag)
288 tags = self.GetTags()
292 raise errors.TagError("Tag not found")
295 """Taggable-object-specific conversion to standard python types.
297 This replaces the tags set with a list.
300 bo = super(TaggableObject, self).ToDict()
302 tags = bo.get("tags", None)
303 if isinstance(tags, set):
304 bo["tags"] = list(tags)
308 def FromDict(cls, val):
309 """Custom function for instances.
312 obj = super(TaggableObject, cls).FromDict(val)
313 if hasattr(obj, "tags") and isinstance(obj.tags, list):
314 obj.tags = set(obj.tags)
318 class ConfigData(ConfigObject):
319 """Top-level config object."""
330 """Custom function for top-level config data.
332 This just replaces the list of instances, nodes and the cluster
333 with standard python types.
336 mydict = super(ConfigData, self).ToDict()
337 mydict["cluster"] = mydict["cluster"].ToDict()
338 for key in "nodes", "instances", "nodegroups":
339 mydict[key] = self._ContainerToDicts(mydict[key])
344 def FromDict(cls, val):
345 """Custom function for top-level config data
348 obj = super(ConfigData, cls).FromDict(val)
349 obj.cluster = Cluster.FromDict(obj.cluster)
350 obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
351 obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
352 obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
355 def HasAnyDiskOfType(self, dev_type):
356 """Check if in there is at disk of the given type in the configuration.
358 @type dev_type: L{constants.LDS_BLOCK}
359 @param dev_type: the type to look for
361 @return: boolean indicating if a disk of the given type was found or not
364 for instance in self.instances.values():
365 for disk in instance.disks:
366 if disk.IsBasedOnDiskType(dev_type):
370 def UpgradeConfig(self):
371 """Fill defaults for missing configuration values.
374 self.cluster.UpgradeConfig()
375 for node in self.nodes.values():
377 for instance in self.instances.values():
378 instance.UpgradeConfig()
379 if self.nodegroups is None:
381 for nodegroup in self.nodegroups.values():
382 nodegroup.UpgradeConfig()
383 if self.cluster.drbd_usermode_helper is None:
384 # To decide if we set an helper let's check if at least one instance has
385 # a DRBD disk. This does not cover all the possible scenarios but it
386 # gives a good approximation.
387 if self.HasAnyDiskOfType(constants.LD_DRBD8):
388 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
391 class NIC(ConfigObject):
392 """Config object representing a network card."""
393 __slots__ = ["mac", "ip", "nicparams"]
396 def CheckParameterSyntax(cls, nicparams):
397 """Check the given parameters for validity.
399 @type nicparams: dict
400 @param nicparams: dictionary with parameter names/value
401 @raise errors.ConfigurationError: when a parameter is not valid
404 if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
405 err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
406 raise errors.ConfigurationError(err)
408 if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
409 not nicparams[constants.NIC_LINK]):
410 err = "Missing bridged nic link"
411 raise errors.ConfigurationError(err)
414 class Disk(ConfigObject):
415 """Config object representing a block device."""
416 __slots__ = ["dev_type", "logical_id", "physical_id",
417 "children", "iv_name", "size", "mode"]
419 def CreateOnSecondary(self):
420 """Test if this device needs to be created on a secondary node."""
421 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
423 def AssembleOnSecondary(self):
424 """Test if this device needs to be assembled on a secondary node."""
425 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
427 def OpenOnSecondary(self):
428 """Test if this device needs to be opened on a secondary node."""
429 return self.dev_type in (constants.LD_LV,)
431 def StaticDevPath(self):
432 """Return the device path if this device type has a static one.
434 Some devices (LVM for example) live always at the same /dev/ path,
435 irrespective of their status. For such devices, we return this
436 path, for others we return None.
438 @warning: The path returned is not a normalized pathname; callers
439 should check that it is a valid path.
442 if self.dev_type == constants.LD_LV:
443 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
446 def ChildrenNeeded(self):
447 """Compute the needed number of children for activation.
449 This method will return either -1 (all children) or a positive
450 number denoting the minimum number of children needed for
451 activation (only mirrored devices will usually return >=0).
453 Currently, only DRBD8 supports diskless activation (therefore we
454 return 0), for all other we keep the previous semantics and return
458 if self.dev_type == constants.LD_DRBD8:
462 def IsBasedOnDiskType(self, dev_type):
463 """Check if the disk or its children are based on the given type.
465 @type dev_type: L{constants.LDS_BLOCK}
466 @param dev_type: the type to look for
468 @return: boolean indicating if a device of the given type was found or not
472 for child in self.children:
473 if child.IsBasedOnDiskType(dev_type):
475 return self.dev_type == dev_type
477 def GetNodes(self, node):
478 """This function returns the nodes this device lives on.
480 Given the node on which the parent of the device lives on (or, in
481 case of a top-level device, the primary node of the devices'
482 instance), this function will return a list of nodes on which this
483 devices needs to (or can) be assembled.
486 if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
488 elif self.dev_type in constants.LDS_DRBD:
489 result = [self.logical_id[0], self.logical_id[1]]
490 if node not in result:
491 raise errors.ConfigurationError("DRBD device passed unknown node")
493 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
496 def ComputeNodeTree(self, parent_node):
497 """Compute the node/disk tree for this disk and its children.
499 This method, given the node on which the parent disk lives, will
500 return the list of all (node, disk) pairs which describe the disk
501 tree in the most compact way. For example, a drbd/lvm stack
502 will be returned as (primary_node, drbd) and (secondary_node, drbd)
503 which represents all the top-level devices on the nodes.
506 my_nodes = self.GetNodes(parent_node)
507 result = [(node, self) for node in my_nodes]
508 if not self.children:
511 for node in my_nodes:
512 for child in self.children:
513 child_result = child.ComputeNodeTree(node)
514 if len(child_result) == 1:
515 # child (and all its descendants) is simple, doesn't split
516 # over multiple hosts, so we don't need to describe it, our
517 # own entry for this node describes it completely
520 # check if child nodes differ from my nodes; note that
521 # subdisk can differ from the child itself, and be instead
522 # one of its descendants
523 for subnode, subdisk in child_result:
524 if subnode not in my_nodes:
525 result.append((subnode, subdisk))
526 # otherwise child is under our own node, so we ignore this
527 # entry (but probably the other results in the list will
531 def RecordGrow(self, amount):
532 """Update the size of this disk after growth.
534 This method recurses over the disks's children and updates their
535 size correspondigly. The method needs to be kept in sync with the
536 actual algorithms from bdev.
539 if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
541 elif self.dev_type == constants.LD_DRBD8:
543 self.children[0].RecordGrow(amount)
546 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
547 " disk type %s" % self.dev_type)
550 """Sets recursively the size to zero for the disk and its children.
554 for child in self.children:
558 def SetPhysicalID(self, target_node, nodes_ip):
559 """Convert the logical ID to the physical ID.
561 This is used only for drbd, which needs ip/port configuration.
563 The routine descends down and updates its children also, because
564 this helps when the only the top device is passed to the remote
568 - target_node: the node we wish to configure for
569 - nodes_ip: a mapping of node name to ip
571 The target_node must exist in in nodes_ip, and must be one of the
572 nodes in the logical ID for each of the DRBD devices encountered
577 for child in self.children:
578 child.SetPhysicalID(target_node, nodes_ip)
580 if self.logical_id is None and self.physical_id is not None:
582 if self.dev_type in constants.LDS_DRBD:
583 pnode, snode, port, pminor, sminor, secret = self.logical_id
584 if target_node not in (pnode, snode):
585 raise errors.ConfigurationError("DRBD device not knowing node %s" %
587 pnode_ip = nodes_ip.get(pnode, None)
588 snode_ip = nodes_ip.get(snode, None)
589 if pnode_ip is None or snode_ip is None:
590 raise errors.ConfigurationError("Can't find primary or secondary node"
591 " for %s" % str(self))
592 p_data = (pnode_ip, port)
593 s_data = (snode_ip, port)
594 if pnode == target_node:
595 self.physical_id = p_data + s_data + (pminor, secret)
596 else: # it must be secondary, we tested above
597 self.physical_id = s_data + p_data + (sminor, secret)
599 self.physical_id = self.logical_id
603 """Disk-specific conversion to standard python types.
605 This replaces the children lists of objects with lists of
606 standard python types.
609 bo = super(Disk, self).ToDict()
611 for attr in ("children",):
612 alist = bo.get(attr, None)
614 bo[attr] = self._ContainerToDicts(alist)
618 def FromDict(cls, val):
619 """Custom function for Disks
622 obj = super(Disk, cls).FromDict(val)
624 obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
625 if obj.logical_id and isinstance(obj.logical_id, list):
626 obj.logical_id = tuple(obj.logical_id)
627 if obj.physical_id and isinstance(obj.physical_id, list):
628 obj.physical_id = tuple(obj.physical_id)
629 if obj.dev_type in constants.LDS_DRBD:
630 # we need a tuple of length six here
631 if len(obj.logical_id) < 6:
632 obj.logical_id += (None,) * (6 - len(obj.logical_id))
636 """Custom str() formatter for disks.
639 if self.dev_type == constants.LD_LV:
640 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
641 elif self.dev_type in constants.LDS_DRBD:
642 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
644 if self.physical_id is None:
647 phy = ("configured as %s:%s %s:%s" %
648 (self.physical_id[0], self.physical_id[1],
649 self.physical_id[2], self.physical_id[3]))
651 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
652 (node_a, minor_a, node_b, minor_b, port, phy))
653 if self.children and self.children.count(None) == 0:
654 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
656 val += "no local storage"
658 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
659 (self.dev_type, self.logical_id, self.physical_id, self.children))
660 if self.iv_name is None:
661 val += ", not visible"
663 val += ", visible as /dev/%s" % self.iv_name
664 if isinstance(self.size, int):
665 val += ", size=%dm)>" % self.size
667 val += ", size='%s')>" % (self.size,)
671 """Checks that this disk is correctly configured.
675 if self.mode not in constants.DISK_ACCESS_SET:
676 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
679 def UpgradeConfig(self):
680 """Fill defaults for missing configuration values.
684 for child in self.children:
685 child.UpgradeConfig()
686 # add here config upgrade for this disk
689 class Instance(TaggableObject):
690 """Config object representing an instance."""
705 ] + _TIMESTAMPS + _UUID
707 def _ComputeSecondaryNodes(self):
708 """Compute the list of secondary nodes.
710 This is a simple wrapper over _ComputeAllNodes.
713 all_nodes = set(self._ComputeAllNodes())
714 all_nodes.discard(self.primary_node)
715 return tuple(all_nodes)
717 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
718 "List of secondary nodes")
720 def _ComputeAllNodes(self):
721 """Compute the list of all nodes.
723 Since the data is already there (in the drbd disks), keeping it as
724 a separate normal attribute is redundant and if not properly
725 synchronised can cause problems. Thus it's better to compute it
729 def _Helper(nodes, device):
730 """Recursively computes nodes given a top device."""
731 if device.dev_type in constants.LDS_DRBD:
732 nodea, nodeb = device.logical_id[:2]
736 for child in device.children:
737 _Helper(nodes, child)
740 all_nodes.add(self.primary_node)
741 for device in self.disks:
742 _Helper(all_nodes, device)
743 return tuple(all_nodes)
745 all_nodes = property(_ComputeAllNodes, None, None,
746 "List of all nodes of the instance")
748 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
749 """Provide a mapping of nodes to LVs this instance owns.
751 This function figures out what logical volumes should belong on
752 which nodes, recursing through a device tree.
754 @param lvmap: optional dictionary to receive the
755 'node' : ['lv', ...] data.
757 @return: None if lvmap arg is given, otherwise, a dictionary of
758 the form { 'nodename' : ['volume1', 'volume2', ...], ... };
759 volumeN is of the form "vg_name/lv_name", compatible with
764 node = self.primary_node
767 lvmap = { node : [] }
770 if not node in lvmap:
778 if dev.dev_type == constants.LD_LV:
779 lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
781 elif dev.dev_type in constants.LDS_DRBD:
783 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
784 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
787 self.MapLVsByNode(lvmap, dev.children, node)
791 def FindDisk(self, idx):
792 """Find a disk given having a specified index.
794 This is just a wrapper that does validation of the index.
797 @param idx: the disk index
799 @return: the corresponding disk
800 @raise errors.OpPrereqError: when the given index is not valid
805 return self.disks[idx]
806 except (TypeError, ValueError), err:
807 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
810 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
811 " 0 to %d" % (idx, len(self.disks)),
815 """Instance-specific conversion to standard python types.
817 This replaces the children lists of objects with lists of standard
821 bo = super(Instance, self).ToDict()
823 for attr in "nics", "disks":
824 alist = bo.get(attr, None)
826 nlist = self._ContainerToDicts(alist)
833 def FromDict(cls, val):
834 """Custom function for instances.
837 obj = super(Instance, cls).FromDict(val)
838 obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
839 obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
842 def UpgradeConfig(self):
843 """Fill defaults for missing configuration values.
846 for nic in self.nics:
848 for disk in self.disks:
851 for key in constants.HVC_GLOBALS:
853 del self.hvparams[key]
856 if self.osparams is None:
860 class OS(ConfigObject):
861 """Config object representing an operating system.
863 @type supported_parameters: list
864 @ivar supported_parameters: a list of tuples, name and description,
865 containing the supported parameters by this OS
867 @type VARIANT_DELIM: string
868 @cvar VARIANT_DELIM: the variant delimiter
880 "supported_variants",
881 "supported_parameters",
887 def SplitNameVariant(cls, name):
888 """Splits the name into the proper name and variant.
890 @param name: the OS (unprocessed) name
892 @return: a list of two elements; if the original name didn't
893 contain a variant, it's returned as an empty string
896 nv = name.split(cls.VARIANT_DELIM, 1)
902 def GetName(cls, name):
903 """Returns the proper name of the os (without the variant).
905 @param name: the OS (unprocessed) name
908 return cls.SplitNameVariant(name)[0]
911 def GetVariant(cls, name):
912 """Returns the variant the os (without the base name).
914 @param name: the OS (unprocessed) name
917 return cls.SplitNameVariant(name)[1]
920 class Node(TaggableObject):
921 """Config object representing a node."""
935 ] + _TIMESTAMPS + _UUID
937 def UpgradeConfig(self):
938 """Fill defaults for missing configuration values.
941 # pylint: disable-msg=E0203
942 # because these are "defined" via slots, not manually
943 if self.master_capable is None:
944 self.master_capable = True
946 if self.vm_capable is None:
947 self.vm_capable = True
949 if self.ndparams is None:
952 if self.powered is None:
956 class NodeGroup(ConfigObject):
957 """Config object representing a node group."""
964 ] + _TIMESTAMPS + _UUID
967 """Custom function for nodegroup.
969 This discards the members object, which gets recalculated and is only kept
973 mydict = super(NodeGroup, self).ToDict()
974 del mydict["members"]
978 def FromDict(cls, val):
979 """Custom function for nodegroup.
981 The members slot is initialized to an empty list, upon deserialization.
984 obj = super(NodeGroup, cls).FromDict(val)
988 def UpgradeConfig(self):
989 """Fill defaults for missing configuration values.
992 if self.ndparams is None:
995 if self.serial_no is None:
998 if self.alloc_policy is None:
999 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1001 # We only update mtime, and not ctime, since we would not be able to provide
1002 # a correct value for creation time.
1003 if self.mtime is None:
1004 self.mtime = time.time()
1006 def FillND(self, node):
1007 """Return filled out ndparams for L{object.Node}
1009 @type node: L{objects.Node}
1010 @param node: A Node object to fill
1011 @return a copy of the node's ndparams with defaults filled
1014 return self.SimpleFillND(node.ndparams)
1016 def SimpleFillND(self, ndparams):
1017 """Fill a given ndparams dict with defaults.
1019 @type ndparams: dict
1020 @param ndparams: the dict to fill
1022 @return: a copy of the passed in ndparams with missing keys filled
1023 from the node group defaults
1026 return FillDict(self.ndparams, ndparams)
1029 class Cluster(TaggableObject):
1030 """Config object representing the cluster."""
1034 "highest_used_port",
1037 "volume_group_name",
1039 "drbd_usermode_helper",
1041 "default_hypervisor",
1047 "enabled_hypervisors",
1054 "candidate_pool_size",
1057 "maintain_node_health",
1059 "default_iallocator",
1062 "primary_ip_family",
1063 "prealloc_wipe_disks",
1064 ] + _TIMESTAMPS + _UUID
1066 def UpgradeConfig(self):
1067 """Fill defaults for missing configuration values.
1070 # pylint: disable-msg=E0203
1071 # because these are "defined" via slots, not manually
1072 if self.hvparams is None:
1073 self.hvparams = constants.HVC_DEFAULTS
1075 for hypervisor in self.hvparams:
1076 self.hvparams[hypervisor] = FillDict(
1077 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1079 if self.os_hvp is None:
1082 # osparams added before 2.2
1083 if self.osparams is None:
1086 if self.ndparams is None:
1087 self.ndparams = constants.NDC_DEFAULTS
1089 self.beparams = UpgradeGroupedParams(self.beparams,
1090 constants.BEC_DEFAULTS)
1091 migrate_default_bridge = not self.nicparams
1092 self.nicparams = UpgradeGroupedParams(self.nicparams,
1093 constants.NICC_DEFAULTS)
1094 if migrate_default_bridge:
1095 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1098 if self.modify_etc_hosts is None:
1099 self.modify_etc_hosts = True
1101 if self.modify_ssh_setup is None:
1102 self.modify_ssh_setup = True
1104 # default_bridge is no longer used it 2.1. The slot is left there to
1105 # support auto-upgrading. It can be removed once we decide to deprecate
1106 # upgrading straight from 2.0.
1107 if self.default_bridge is not None:
1108 self.default_bridge = None
1110 # default_hypervisor is just the first enabled one in 2.1. This slot and
1111 # code can be removed once upgrading straight from 2.0 is deprecated.
1112 if self.default_hypervisor is not None:
1113 self.enabled_hypervisors = ([self.default_hypervisor] +
1114 [hvname for hvname in self.enabled_hypervisors
1115 if hvname != self.default_hypervisor])
1116 self.default_hypervisor = None
1118 # maintain_node_health added after 2.1.1
1119 if self.maintain_node_health is None:
1120 self.maintain_node_health = False
1122 if self.uid_pool is None:
1125 if self.default_iallocator is None:
1126 self.default_iallocator = ""
1128 # reserved_lvs added before 2.2
1129 if self.reserved_lvs is None:
1130 self.reserved_lvs = []
1132 # hidden and blacklisted operating systems added before 2.2.1
1133 if self.hidden_os is None:
1136 if self.blacklisted_os is None:
1137 self.blacklisted_os = []
1139 # primary_ip_family added before 2.3
1140 if self.primary_ip_family is None:
1141 self.primary_ip_family = AF_INET
1143 if self.prealloc_wipe_disks is None:
1144 self.prealloc_wipe_disks = False
1147 """Custom function for cluster.
1150 mydict = super(Cluster, self).ToDict()
1151 mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1155 def FromDict(cls, val):
1156 """Custom function for cluster.
1159 obj = super(Cluster, cls).FromDict(val)
1160 if not isinstance(obj.tcpudp_port_pool, set):
1161 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1164 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1165 """Get the default hypervisor parameters for the cluster.
1167 @param hypervisor: the hypervisor name
1168 @param os_name: if specified, we'll also update the defaults for this OS
1169 @param skip_keys: if passed, list of keys not to use
1170 @return: the defaults dict
1173 if skip_keys is None:
1176 fill_stack = [self.hvparams.get(hypervisor, {})]
1177 if os_name is not None:
1178 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1179 fill_stack.append(os_hvp)
1182 for o_dict in fill_stack:
1183 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1187 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1188 """Fill a given hvparams dict with cluster defaults.
1190 @type hv_name: string
1191 @param hv_name: the hypervisor to use
1192 @type os_name: string
1193 @param os_name: the OS to use for overriding the hypervisor defaults
1194 @type skip_globals: boolean
1195 @param skip_globals: if True, the global hypervisor parameters will
1198 @return: a copy of the given hvparams with missing keys filled from
1199 the cluster defaults
1203 skip_keys = constants.HVC_GLOBALS
1207 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1208 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1210 def FillHV(self, instance, skip_globals=False):
1211 """Fill an instance's hvparams dict with cluster defaults.
1213 @type instance: L{objects.Instance}
1214 @param instance: the instance parameter to fill
1215 @type skip_globals: boolean
1216 @param skip_globals: if True, the global hypervisor parameters will
1219 @return: a copy of the instance's hvparams with missing keys filled from
1220 the cluster defaults
1223 return self.SimpleFillHV(instance.hypervisor, instance.os,
1224 instance.hvparams, skip_globals)
1226 def SimpleFillBE(self, beparams):
1227 """Fill a given beparams dict with cluster defaults.
1229 @type beparams: dict
1230 @param beparams: the dict to fill
1232 @return: a copy of the passed in beparams with missing keys filled
1233 from the cluster defaults
1236 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1238 def FillBE(self, instance):
1239 """Fill an instance's beparams dict with cluster defaults.
1241 @type instance: L{objects.Instance}
1242 @param instance: the instance parameter to fill
1244 @return: a copy of the instance's beparams with missing keys filled from
1245 the cluster defaults
1248 return self.SimpleFillBE(instance.beparams)
1250 def SimpleFillNIC(self, nicparams):
1251 """Fill a given nicparams dict with cluster defaults.
1253 @type nicparams: dict
1254 @param nicparams: the dict to fill
1256 @return: a copy of the passed in nicparams with missing keys filled
1257 from the cluster defaults
1260 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1262 def SimpleFillOS(self, os_name, os_params):
1263 """Fill an instance's osparams dict with cluster defaults.
1265 @type os_name: string
1266 @param os_name: the OS name to use
1267 @type os_params: dict
1268 @param os_params: the dict to fill with default values
1270 @return: a copy of the instance's osparams with missing keys filled from
1271 the cluster defaults
1274 name_only = os_name.split("+", 1)[0]
1276 result = self.osparams.get(name_only, {})
1278 result = FillDict(result, self.osparams.get(os_name, {}))
1280 return FillDict(result, os_params)
1282 def FillND(self, node, nodegroup):
1283 """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node}
1285 @type node: L{objects.Node}
1286 @param node: A Node object to fill
1287 @type nodegroup: L{objects.NodeGroup}
1288 @param nodegroup: A Node object to fill
1289 @return a copy of the node's ndparams with defaults filled
1292 return self.SimpleFillND(nodegroup.FillND(node))
1294 def SimpleFillND(self, ndparams):
1295 """Fill a given ndparams dict with defaults.
1297 @type ndparams: dict
1298 @param ndparams: the dict to fill
1300 @return: a copy of the passed in ndparams with missing keys filled
1301 from the cluster defaults
1304 return FillDict(self.ndparams, ndparams)
1307 class BlockDevStatus(ConfigObject):
1308 """Config object representing the status of a block device."""
1320 class ImportExportStatus(ConfigObject):
1321 """Config object representing the status of an import or export."""
1327 "progress_throughput",
1335 class ImportExportOptions(ConfigObject):
1336 """Options for import/export daemon
1338 @ivar key_name: X509 key name (None for cluster certificate)
1339 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1340 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1341 @ivar magic: Used to ensure the connection goes to the right disk
1342 @ivar ipv6: Whether to use IPv6
1354 class ConfdRequest(ConfigObject):
1355 """Object holding a confd request.
1357 @ivar protocol: confd protocol version
1358 @ivar type: confd query type
1359 @ivar query: query request
1360 @ivar rsalt: requested reply salt
1371 class ConfdReply(ConfigObject):
1372 """Object holding a confd reply.
1374 @ivar protocol: confd protocol version
1375 @ivar status: reply status code (ok, error)
1376 @ivar answer: confd query reply
1377 @ivar serial: configuration serial number
1388 class QueryFieldDefinition(ConfigObject):
1389 """Object holding a query field definition.
1391 @ivar name: Field name
1392 @ivar title: Human-readable title
1393 @ivar kind: Field type
1403 class _QueryResponseBase(ConfigObject):
1409 """Custom function for serializing.
1412 mydict = super(_QueryResponseBase, self).ToDict()
1413 mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1417 def FromDict(cls, val):
1418 """Custom function for de-serializing.
1421 obj = super(_QueryResponseBase, cls).FromDict(val)
1422 obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1426 class QueryRequest(ConfigObject):
1427 """Object holding a query request.
1437 class QueryResponse(_QueryResponseBase):
1438 """Object holding the response to a query.
1440 @ivar fields: List of L{QueryFieldDefinition} objects
1441 @ivar data: Requested data
1449 class QueryFieldsRequest(ConfigObject):
1450 """Object holding a request for querying available fields.
1459 class QueryFieldsResponse(_QueryResponseBase):
1460 """Object holding the response to a query for fields.
1462 @ivar fields: List of L{QueryFieldDefinition} objects
1469 class InstanceConsole(ConfigObject):
1470 """Object describing how to access the console of an instance.
1485 """Validates contents of this object.
1488 assert self.kind in constants.CONS_ALL, "Unknown console type"
1489 assert self.instance, "Missing instance name"
1490 assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
1491 assert self.host or self.kind == constants.CONS_MESSAGE
1492 assert self.port or self.kind in [constants.CONS_MESSAGE,
1494 assert self.user or self.kind in [constants.CONS_MESSAGE,
1496 assert self.command or self.kind in [constants.CONS_MESSAGE,
1498 assert self.display or self.kind in [constants.CONS_MESSAGE,
1503 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1504 """Simple wrapper over ConfigParse that allows serialization.
1506 This class is basically ConfigParser.SafeConfigParser with two
1507 additional methods that allow it to serialize/unserialize to/from a
1512 """Dump this instance and return the string representation."""
1515 return buf.getvalue()
1518 def Loads(cls, data):
1519 """Load data from a string."""
1520 buf = StringIO(data)