4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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=E0203,W0201,R0902
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__
36 # R0902: Allow instances of these objects to have more than 20 attributes
42 from cStringIO import StringIO
44 from ganeti import errors
45 from ganeti import constants
46 from ganeti import netutils
48 from socket import AF_INET
51 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
52 "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
54 _TIMESTAMPS = ["ctime", "mtime"]
58 def FillDict(defaults_dict, custom_dict, skip_keys=None):
59 """Basic function to apply settings on top a default dict.
61 @type defaults_dict: dict
62 @param defaults_dict: dictionary holding the default values
63 @type custom_dict: dict
64 @param custom_dict: dictionary holding customized value
66 @param skip_keys: which keys not to fill
68 @return: dict with the 'full' values
71 ret_dict = copy.deepcopy(defaults_dict)
72 ret_dict.update(custom_dict)
82 def UpgradeGroupedParams(target, defaults):
83 """Update all groups for the target parameter.
85 @type target: dict of dicts
86 @param target: {group: {parameter: value}}
88 @param defaults: default parameter values
92 target = {constants.PP_DEFAULT: defaults}
95 target[group] = FillDict(defaults, target[group])
99 def UpgradeBeParams(target):
100 """Update the be parameters dict to the new format.
103 @param target: "be" parameters dict
106 if constants.BE_MEMORY in target:
107 memory = target[constants.BE_MEMORY]
108 target[constants.BE_MAXMEM] = memory
109 target[constants.BE_MINMEM] = memory
110 del target[constants.BE_MEMORY]
113 class ConfigObject(object):
114 """A generic config object.
116 It has the following properties:
118 - provides somewhat safe recursive unpickling and pickling for its classes
119 - unset attributes which are defined in slots are always returned
120 as None instead of raising an error
122 Classes derived from this must always declare __slots__ (we use many
123 config objects and the memory reduction is useful)
128 def __init__(self, **kwargs):
129 for k, v in kwargs.iteritems():
132 def __getattr__(self, name):
133 if name not in self._all_slots():
134 raise AttributeError("Invalid object attribute %s.%s" %
135 (type(self).__name__, name))
138 def __setstate__(self, state):
139 slots = self._all_slots()
142 setattr(self, name, state[name])
146 """Compute the list of all declared slots for a class.
150 for parent in cls.__mro__:
151 slots.extend(getattr(parent, "__slots__", []))
155 """Convert to a dict holding only standard python types.
157 The generic routine just dumps all of this object's attributes in
158 a dict. It does not work if the class has children who are
159 ConfigObjects themselves (e.g. the nics list in an Instance), in
160 which case the object should subclass the function in order to
161 make sure all objects returned are only standard python types.
165 for name in self._all_slots():
166 value = getattr(self, name, None)
167 if value is not None:
171 __getstate__ = ToDict
174 def FromDict(cls, val):
175 """Create an object from a dictionary.
177 This generic routine takes a dict, instantiates a new instance of
178 the given class, and sets attributes based on the dict content.
180 As for `ToDict`, this does not work if the class has children
181 who are ConfigObjects themselves (e.g. the nics list in an
182 Instance), in which case the object should subclass the function
183 and alter the objects.
186 if not isinstance(val, dict):
187 raise errors.ConfigurationError("Invalid object passed to FromDict:"
188 " expected dict, got %s" % type(val))
189 val_str = dict([(str(k), v) for k, v in val.iteritems()])
190 obj = cls(**val_str) # pylint: disable=W0142
194 def _ContainerToDicts(container):
195 """Convert the elements of a container to standard python types.
197 This method converts a container with elements derived from
198 ConfigData to standard python types. If the container is a dict,
199 we don't touch the keys, only the values.
202 if isinstance(container, dict):
203 ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
204 elif isinstance(container, (list, tuple, set, frozenset)):
205 ret = [elem.ToDict() for elem in container]
207 raise TypeError("Invalid type %s passed to _ContainerToDicts" %
212 def _ContainerFromDicts(source, c_type, e_type):
213 """Convert a container from standard python types.
215 This method converts a container with standard python types to
216 ConfigData objects. If the container is a dict, we don't touch the
217 keys, only the values.
220 if not isinstance(c_type, type):
221 raise TypeError("Container type %s passed to _ContainerFromDicts is"
222 " not a type" % type(c_type))
226 ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
227 elif c_type in (list, tuple, set, frozenset):
228 ret = c_type([e_type.FromDict(elem) for elem in source])
230 raise TypeError("Invalid container type %s passed to"
231 " _ContainerFromDicts" % c_type)
235 """Makes a deep copy of the current object and its children.
238 dict_form = self.ToDict()
239 clone_obj = self.__class__.FromDict(dict_form)
243 """Implement __repr__ for ConfigObjects."""
244 return repr(self.ToDict())
246 def UpgradeConfig(self):
247 """Fill defaults for missing configuration values.
249 This method will be called at configuration load time, and its
250 implementation will be object dependent.
256 class TaggableObject(ConfigObject):
257 """An generic class supporting tags.
261 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
264 def ValidateTag(cls, tag):
265 """Check if a tag is valid.
267 If the tag is invalid, an errors.TagError will be raised. The
268 function has no return value.
271 if not isinstance(tag, basestring):
272 raise errors.TagError("Invalid tag type (not a string)")
273 if len(tag) > constants.MAX_TAG_LEN:
274 raise errors.TagError("Tag too long (>%d characters)" %
275 constants.MAX_TAG_LEN)
277 raise errors.TagError("Tags cannot be empty")
278 if not cls.VALID_TAG_RE.match(tag):
279 raise errors.TagError("Tag contains invalid characters")
282 """Return the tags list.
285 tags = getattr(self, "tags", None)
287 tags = self.tags = set()
290 def AddTag(self, tag):
294 self.ValidateTag(tag)
295 tags = self.GetTags()
296 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
297 raise errors.TagError("Too many tags")
298 self.GetTags().add(tag)
300 def RemoveTag(self, tag):
304 self.ValidateTag(tag)
305 tags = self.GetTags()
309 raise errors.TagError("Tag not found")
312 """Taggable-object-specific conversion to standard python types.
314 This replaces the tags set with a list.
317 bo = super(TaggableObject, self).ToDict()
319 tags = bo.get("tags", None)
320 if isinstance(tags, set):
321 bo["tags"] = list(tags)
325 def FromDict(cls, val):
326 """Custom function for instances.
329 obj = super(TaggableObject, cls).FromDict(val)
330 if hasattr(obj, "tags") and isinstance(obj.tags, list):
331 obj.tags = set(obj.tags)
335 class MasterNetworkParameters(ConfigObject):
336 """Network configuration parameters for the master
338 @ivar name: master name
340 @ivar netmask: master netmask
341 @ivar netdev: master network device
342 @ivar ip_family: master IP family
354 class ConfigData(ConfigObject):
355 """Top-level config object."""
366 """Custom function for top-level config data.
368 This just replaces the list of instances, nodes and the cluster
369 with standard python types.
372 mydict = super(ConfigData, self).ToDict()
373 mydict["cluster"] = mydict["cluster"].ToDict()
374 for key in "nodes", "instances", "nodegroups":
375 mydict[key] = self._ContainerToDicts(mydict[key])
380 def FromDict(cls, val):
381 """Custom function for top-level config data
384 obj = super(ConfigData, cls).FromDict(val)
385 obj.cluster = Cluster.FromDict(obj.cluster)
386 obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
387 obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
388 obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
391 def HasAnyDiskOfType(self, dev_type):
392 """Check if in there is at disk of the given type in the configuration.
394 @type dev_type: L{constants.LDS_BLOCK}
395 @param dev_type: the type to look for
397 @return: boolean indicating if a disk of the given type was found or not
400 for instance in self.instances.values():
401 for disk in instance.disks:
402 if disk.IsBasedOnDiskType(dev_type):
406 def UpgradeConfig(self):
407 """Fill defaults for missing configuration values.
410 self.cluster.UpgradeConfig()
411 for node in self.nodes.values():
413 for instance in self.instances.values():
414 instance.UpgradeConfig()
415 if self.nodegroups is None:
417 for nodegroup in self.nodegroups.values():
418 nodegroup.UpgradeConfig()
419 if self.cluster.drbd_usermode_helper is None:
420 # To decide if we set an helper let's check if at least one instance has
421 # a DRBD disk. This does not cover all the possible scenarios but it
422 # gives a good approximation.
423 if self.HasAnyDiskOfType(constants.LD_DRBD8):
424 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
427 class NIC(ConfigObject):
428 """Config object representing a network card."""
429 __slots__ = ["mac", "ip", "nicparams"]
432 def CheckParameterSyntax(cls, nicparams):
433 """Check the given parameters for validity.
435 @type nicparams: dict
436 @param nicparams: dictionary with parameter names/value
437 @raise errors.ConfigurationError: when a parameter is not valid
440 if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
441 nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
442 err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
443 raise errors.ConfigurationError(err)
445 if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
446 not nicparams[constants.NIC_LINK]):
447 err = "Missing bridged nic link"
448 raise errors.ConfigurationError(err)
451 class Disk(ConfigObject):
452 """Config object representing a block device."""
453 __slots__ = ["dev_type", "logical_id", "physical_id",
454 "children", "iv_name", "size", "mode"]
456 def CreateOnSecondary(self):
457 """Test if this device needs to be created on a secondary node."""
458 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
460 def AssembleOnSecondary(self):
461 """Test if this device needs to be assembled on a secondary node."""
462 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
464 def OpenOnSecondary(self):
465 """Test if this device needs to be opened on a secondary node."""
466 return self.dev_type in (constants.LD_LV,)
468 def StaticDevPath(self):
469 """Return the device path if this device type has a static one.
471 Some devices (LVM for example) live always at the same /dev/ path,
472 irrespective of their status. For such devices, we return this
473 path, for others we return None.
475 @warning: The path returned is not a normalized pathname; callers
476 should check that it is a valid path.
479 if self.dev_type == constants.LD_LV:
480 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
481 elif self.dev_type == constants.LD_BLOCKDEV:
482 return self.logical_id[1]
485 def ChildrenNeeded(self):
486 """Compute the needed number of children for activation.
488 This method will return either -1 (all children) or a positive
489 number denoting the minimum number of children needed for
490 activation (only mirrored devices will usually return >=0).
492 Currently, only DRBD8 supports diskless activation (therefore we
493 return 0), for all other we keep the previous semantics and return
497 if self.dev_type == constants.LD_DRBD8:
501 def IsBasedOnDiskType(self, dev_type):
502 """Check if the disk or its children are based on the given type.
504 @type dev_type: L{constants.LDS_BLOCK}
505 @param dev_type: the type to look for
507 @return: boolean indicating if a device of the given type was found or not
511 for child in self.children:
512 if child.IsBasedOnDiskType(dev_type):
514 return self.dev_type == dev_type
516 def GetNodes(self, node):
517 """This function returns the nodes this device lives on.
519 Given the node on which the parent of the device lives on (or, in
520 case of a top-level device, the primary node of the devices'
521 instance), this function will return a list of nodes on which this
522 devices needs to (or can) be assembled.
525 if self.dev_type in [constants.LD_LV, constants.LD_FILE,
526 constants.LD_BLOCKDEV]:
528 elif self.dev_type in constants.LDS_DRBD:
529 result = [self.logical_id[0], self.logical_id[1]]
530 if node not in result:
531 raise errors.ConfigurationError("DRBD device passed unknown node")
533 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
536 def ComputeNodeTree(self, parent_node):
537 """Compute the node/disk tree for this disk and its children.
539 This method, given the node on which the parent disk lives, will
540 return the list of all (node, disk) pairs which describe the disk
541 tree in the most compact way. For example, a drbd/lvm stack
542 will be returned as (primary_node, drbd) and (secondary_node, drbd)
543 which represents all the top-level devices on the nodes.
546 my_nodes = self.GetNodes(parent_node)
547 result = [(node, self) for node in my_nodes]
548 if not self.children:
551 for node in my_nodes:
552 for child in self.children:
553 child_result = child.ComputeNodeTree(node)
554 if len(child_result) == 1:
555 # child (and all its descendants) is simple, doesn't split
556 # over multiple hosts, so we don't need to describe it, our
557 # own entry for this node describes it completely
560 # check if child nodes differ from my nodes; note that
561 # subdisk can differ from the child itself, and be instead
562 # one of its descendants
563 for subnode, subdisk in child_result:
564 if subnode not in my_nodes:
565 result.append((subnode, subdisk))
566 # otherwise child is under our own node, so we ignore this
567 # entry (but probably the other results in the list will
571 def ComputeGrowth(self, amount):
572 """Compute the per-VG growth requirements.
574 This only works for VG-based disks.
576 @type amount: integer
577 @param amount: the desired increase in (user-visible) disk space
579 @return: a dictionary of volume-groups and the required size
582 if self.dev_type == constants.LD_LV:
583 return {self.logical_id[0]: amount}
584 elif self.dev_type == constants.LD_DRBD8:
586 return self.children[0].ComputeGrowth(amount)
590 # Other disk types do not require VG space
593 def RecordGrow(self, amount):
594 """Update the size of this disk after growth.
596 This method recurses over the disks's children and updates their
597 size correspondigly. The method needs to be kept in sync with the
598 actual algorithms from bdev.
601 if self.dev_type in (constants.LD_LV, constants.LD_FILE):
603 elif self.dev_type == constants.LD_DRBD8:
605 self.children[0].RecordGrow(amount)
608 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
609 " disk type %s" % self.dev_type)
612 """Sets recursively the size to zero for the disk and its children.
616 for child in self.children:
620 def SetPhysicalID(self, target_node, nodes_ip):
621 """Convert the logical ID to the physical ID.
623 This is used only for drbd, which needs ip/port configuration.
625 The routine descends down and updates its children also, because
626 this helps when the only the top device is passed to the remote
630 - target_node: the node we wish to configure for
631 - nodes_ip: a mapping of node name to ip
633 The target_node must exist in in nodes_ip, and must be one of the
634 nodes in the logical ID for each of the DRBD devices encountered
639 for child in self.children:
640 child.SetPhysicalID(target_node, nodes_ip)
642 if self.logical_id is None and self.physical_id is not None:
644 if self.dev_type in constants.LDS_DRBD:
645 pnode, snode, port, pminor, sminor, secret = self.logical_id
646 if target_node not in (pnode, snode):
647 raise errors.ConfigurationError("DRBD device not knowing node %s" %
649 pnode_ip = nodes_ip.get(pnode, None)
650 snode_ip = nodes_ip.get(snode, None)
651 if pnode_ip is None or snode_ip is None:
652 raise errors.ConfigurationError("Can't find primary or secondary node"
653 " for %s" % str(self))
654 p_data = (pnode_ip, port)
655 s_data = (snode_ip, port)
656 if pnode == target_node:
657 self.physical_id = p_data + s_data + (pminor, secret)
658 else: # it must be secondary, we tested above
659 self.physical_id = s_data + p_data + (sminor, secret)
661 self.physical_id = self.logical_id
665 """Disk-specific conversion to standard python types.
667 This replaces the children lists of objects with lists of
668 standard python types.
671 bo = super(Disk, self).ToDict()
673 for attr in ("children",):
674 alist = bo.get(attr, None)
676 bo[attr] = self._ContainerToDicts(alist)
680 def FromDict(cls, val):
681 """Custom function for Disks
684 obj = super(Disk, cls).FromDict(val)
686 obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
687 if obj.logical_id and isinstance(obj.logical_id, list):
688 obj.logical_id = tuple(obj.logical_id)
689 if obj.physical_id and isinstance(obj.physical_id, list):
690 obj.physical_id = tuple(obj.physical_id)
691 if obj.dev_type in constants.LDS_DRBD:
692 # we need a tuple of length six here
693 if len(obj.logical_id) < 6:
694 obj.logical_id += (None,) * (6 - len(obj.logical_id))
698 """Custom str() formatter for disks.
701 if self.dev_type == constants.LD_LV:
702 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
703 elif self.dev_type in constants.LDS_DRBD:
704 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
706 if self.physical_id is None:
709 phy = ("configured as %s:%s %s:%s" %
710 (self.physical_id[0], self.physical_id[1],
711 self.physical_id[2], self.physical_id[3]))
713 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
714 (node_a, minor_a, node_b, minor_b, port, phy))
715 if self.children and self.children.count(None) == 0:
716 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
718 val += "no local storage"
720 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
721 (self.dev_type, self.logical_id, self.physical_id, self.children))
722 if self.iv_name is None:
723 val += ", not visible"
725 val += ", visible as /dev/%s" % self.iv_name
726 if isinstance(self.size, int):
727 val += ", size=%dm)>" % self.size
729 val += ", size='%s')>" % (self.size,)
733 """Checks that this disk is correctly configured.
737 if self.mode not in constants.DISK_ACCESS_SET:
738 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
741 def UpgradeConfig(self):
742 """Fill defaults for missing configuration values.
746 for child in self.children:
747 child.UpgradeConfig()
748 # add here config upgrade for this disk
751 class Instance(TaggableObject):
752 """Config object representing an instance."""
767 ] + _TIMESTAMPS + _UUID
769 def _ComputeSecondaryNodes(self):
770 """Compute the list of secondary nodes.
772 This is a simple wrapper over _ComputeAllNodes.
775 all_nodes = set(self._ComputeAllNodes())
776 all_nodes.discard(self.primary_node)
777 return tuple(all_nodes)
779 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
780 "List of secondary nodes")
782 def _ComputeAllNodes(self):
783 """Compute the list of all nodes.
785 Since the data is already there (in the drbd disks), keeping it as
786 a separate normal attribute is redundant and if not properly
787 synchronised can cause problems. Thus it's better to compute it
791 def _Helper(nodes, device):
792 """Recursively computes nodes given a top device."""
793 if device.dev_type in constants.LDS_DRBD:
794 nodea, nodeb = device.logical_id[:2]
798 for child in device.children:
799 _Helper(nodes, child)
802 all_nodes.add(self.primary_node)
803 for device in self.disks:
804 _Helper(all_nodes, device)
805 return tuple(all_nodes)
807 all_nodes = property(_ComputeAllNodes, None, None,
808 "List of all nodes of the instance")
810 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
811 """Provide a mapping of nodes to LVs this instance owns.
813 This function figures out what logical volumes should belong on
814 which nodes, recursing through a device tree.
816 @param lvmap: optional dictionary to receive the
817 'node' : ['lv', ...] data.
819 @return: None if lvmap arg is given, otherwise, a dictionary of
820 the form { 'nodename' : ['volume1', 'volume2', ...], ... };
821 volumeN is of the form "vg_name/lv_name", compatible with
826 node = self.primary_node
834 if not node in lvmap:
842 if dev.dev_type == constants.LD_LV:
843 lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
845 elif dev.dev_type in constants.LDS_DRBD:
847 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
848 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
851 self.MapLVsByNode(lvmap, dev.children, node)
855 def FindDisk(self, idx):
856 """Find a disk given having a specified index.
858 This is just a wrapper that does validation of the index.
861 @param idx: the disk index
863 @return: the corresponding disk
864 @raise errors.OpPrereqError: when the given index is not valid
869 return self.disks[idx]
870 except (TypeError, ValueError), err:
871 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
874 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
875 " 0 to %d" % (idx, len(self.disks) - 1),
879 """Instance-specific conversion to standard python types.
881 This replaces the children lists of objects with lists of standard
885 bo = super(Instance, self).ToDict()
887 for attr in "nics", "disks":
888 alist = bo.get(attr, None)
890 nlist = self._ContainerToDicts(alist)
897 def FromDict(cls, val):
898 """Custom function for instances.
901 if "admin_state" not in val:
902 if val.get("admin_up", False):
903 val["admin_state"] = constants.ADMINST_UP
905 val["admin_state"] = constants.ADMINST_DOWN
906 if "admin_up" in val:
908 obj = super(Instance, cls).FromDict(val)
909 obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
910 obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
913 def UpgradeConfig(self):
914 """Fill defaults for missing configuration values.
917 for nic in self.nics:
919 for disk in self.disks:
922 for key in constants.HVC_GLOBALS:
924 del self.hvparams[key]
927 if self.osparams is None:
929 UpgradeBeParams(self.beparams)
932 class OS(ConfigObject):
933 """Config object representing an operating system.
935 @type supported_parameters: list
936 @ivar supported_parameters: a list of tuples, name and description,
937 containing the supported parameters by this OS
939 @type VARIANT_DELIM: string
940 @cvar VARIANT_DELIM: the variant delimiter
952 "supported_variants",
953 "supported_parameters",
959 def SplitNameVariant(cls, name):
960 """Splits the name into the proper name and variant.
962 @param name: the OS (unprocessed) name
964 @return: a list of two elements; if the original name didn't
965 contain a variant, it's returned as an empty string
968 nv = name.split(cls.VARIANT_DELIM, 1)
974 def GetName(cls, name):
975 """Returns the proper name of the os (without the variant).
977 @param name: the OS (unprocessed) name
980 return cls.SplitNameVariant(name)[0]
983 def GetVariant(cls, name):
984 """Returns the variant the os (without the base name).
986 @param name: the OS (unprocessed) name
989 return cls.SplitNameVariant(name)[1]
992 class Node(TaggableObject):
993 """Config object representing a node."""
1009 ] + _TIMESTAMPS + _UUID
1011 def UpgradeConfig(self):
1012 """Fill defaults for missing configuration values.
1015 # pylint: disable=E0203
1016 # because these are "defined" via slots, not manually
1017 if self.master_capable is None:
1018 self.master_capable = True
1020 if self.vm_capable is None:
1021 self.vm_capable = True
1023 if self.ndparams is None:
1026 if self.powered is None:
1030 class NodeGroup(TaggableObject):
1031 """Config object representing a node group."""
1038 ] + _TIMESTAMPS + _UUID
1041 """Custom function for nodegroup.
1043 This discards the members object, which gets recalculated and is only kept
1047 mydict = super(NodeGroup, self).ToDict()
1048 del mydict["members"]
1052 def FromDict(cls, val):
1053 """Custom function for nodegroup.
1055 The members slot is initialized to an empty list, upon deserialization.
1058 obj = super(NodeGroup, cls).FromDict(val)
1062 def UpgradeConfig(self):
1063 """Fill defaults for missing configuration values.
1066 if self.ndparams is None:
1069 if self.serial_no is None:
1072 if self.alloc_policy is None:
1073 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1075 # We only update mtime, and not ctime, since we would not be able to provide
1076 # a correct value for creation time.
1077 if self.mtime is None:
1078 self.mtime = time.time()
1080 def FillND(self, node):
1081 """Return filled out ndparams for L{objects.Node}
1083 @type node: L{objects.Node}
1084 @param node: A Node object to fill
1085 @return a copy of the node's ndparams with defaults filled
1088 return self.SimpleFillND(node.ndparams)
1090 def SimpleFillND(self, ndparams):
1091 """Fill a given ndparams dict with defaults.
1093 @type ndparams: dict
1094 @param ndparams: the dict to fill
1096 @return: a copy of the passed in ndparams with missing keys filled
1097 from the node group defaults
1100 return FillDict(self.ndparams, ndparams)
1103 class Cluster(TaggableObject):
1104 """Config object representing the cluster."""
1108 "highest_used_port",
1111 "volume_group_name",
1113 "drbd_usermode_helper",
1115 "default_hypervisor",
1120 "use_external_mip_script",
1123 "shared_file_storage_dir",
1124 "enabled_hypervisors",
1131 "candidate_pool_size",
1134 "maintain_node_health",
1136 "default_iallocator",
1139 "primary_ip_family",
1140 "prealloc_wipe_disks",
1141 ] + _TIMESTAMPS + _UUID
1143 def UpgradeConfig(self):
1144 """Fill defaults for missing configuration values.
1147 # pylint: disable=E0203
1148 # because these are "defined" via slots, not manually
1149 if self.hvparams is None:
1150 self.hvparams = constants.HVC_DEFAULTS
1152 for hypervisor in self.hvparams:
1153 self.hvparams[hypervisor] = FillDict(
1154 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1156 if self.os_hvp is None:
1159 # osparams added before 2.2
1160 if self.osparams is None:
1163 if self.ndparams is None:
1164 self.ndparams = constants.NDC_DEFAULTS
1166 self.beparams = UpgradeGroupedParams(self.beparams,
1167 constants.BEC_DEFAULTS)
1168 for beparams_group in self.beparams:
1169 UpgradeBeParams(self.beparams[beparams_group])
1171 migrate_default_bridge = not self.nicparams
1172 self.nicparams = UpgradeGroupedParams(self.nicparams,
1173 constants.NICC_DEFAULTS)
1174 if migrate_default_bridge:
1175 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1178 if self.modify_etc_hosts is None:
1179 self.modify_etc_hosts = True
1181 if self.modify_ssh_setup is None:
1182 self.modify_ssh_setup = True
1184 # default_bridge is no longer used in 2.1. The slot is left there to
1185 # support auto-upgrading. It can be removed once we decide to deprecate
1186 # upgrading straight from 2.0.
1187 if self.default_bridge is not None:
1188 self.default_bridge = None
1190 # default_hypervisor is just the first enabled one in 2.1. This slot and
1191 # code can be removed once upgrading straight from 2.0 is deprecated.
1192 if self.default_hypervisor is not None:
1193 self.enabled_hypervisors = ([self.default_hypervisor] +
1194 [hvname for hvname in self.enabled_hypervisors
1195 if hvname != self.default_hypervisor])
1196 self.default_hypervisor = None
1198 # maintain_node_health added after 2.1.1
1199 if self.maintain_node_health is None:
1200 self.maintain_node_health = False
1202 if self.uid_pool is None:
1205 if self.default_iallocator is None:
1206 self.default_iallocator = ""
1208 # reserved_lvs added before 2.2
1209 if self.reserved_lvs is None:
1210 self.reserved_lvs = []
1212 # hidden and blacklisted operating systems added before 2.2.1
1213 if self.hidden_os is None:
1216 if self.blacklisted_os is None:
1217 self.blacklisted_os = []
1219 # primary_ip_family added before 2.3
1220 if self.primary_ip_family is None:
1221 self.primary_ip_family = AF_INET
1223 if self.master_netmask is None:
1224 ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1225 self.master_netmask = ipcls.iplen
1227 if self.prealloc_wipe_disks is None:
1228 self.prealloc_wipe_disks = False
1230 # shared_file_storage_dir added before 2.5
1231 if self.shared_file_storage_dir is None:
1232 self.shared_file_storage_dir = ""
1234 if self.use_external_mip_script is None:
1235 self.use_external_mip_script = False
1238 """Custom function for cluster.
1241 mydict = super(Cluster, self).ToDict()
1242 mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1246 def FromDict(cls, val):
1247 """Custom function for cluster.
1250 obj = super(Cluster, cls).FromDict(val)
1251 if not isinstance(obj.tcpudp_port_pool, set):
1252 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1255 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1256 """Get the default hypervisor parameters for the cluster.
1258 @param hypervisor: the hypervisor name
1259 @param os_name: if specified, we'll also update the defaults for this OS
1260 @param skip_keys: if passed, list of keys not to use
1261 @return: the defaults dict
1264 if skip_keys is None:
1267 fill_stack = [self.hvparams.get(hypervisor, {})]
1268 if os_name is not None:
1269 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1270 fill_stack.append(os_hvp)
1273 for o_dict in fill_stack:
1274 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1278 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1279 """Fill a given hvparams dict with cluster defaults.
1281 @type hv_name: string
1282 @param hv_name: the hypervisor to use
1283 @type os_name: string
1284 @param os_name: the OS to use for overriding the hypervisor defaults
1285 @type skip_globals: boolean
1286 @param skip_globals: if True, the global hypervisor parameters will
1289 @return: a copy of the given hvparams with missing keys filled from
1290 the cluster defaults
1294 skip_keys = constants.HVC_GLOBALS
1298 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1299 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1301 def FillHV(self, instance, skip_globals=False):
1302 """Fill an instance's hvparams dict with cluster defaults.
1304 @type instance: L{objects.Instance}
1305 @param instance: the instance parameter to fill
1306 @type skip_globals: boolean
1307 @param skip_globals: if True, the global hypervisor parameters will
1310 @return: a copy of the instance's hvparams with missing keys filled from
1311 the cluster defaults
1314 return self.SimpleFillHV(instance.hypervisor, instance.os,
1315 instance.hvparams, skip_globals)
1317 def SimpleFillBE(self, beparams):
1318 """Fill a given beparams dict with cluster defaults.
1320 @type beparams: dict
1321 @param beparams: the dict to fill
1323 @return: a copy of the passed in beparams with missing keys filled
1324 from the cluster defaults
1327 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1329 def FillBE(self, instance):
1330 """Fill an instance's beparams dict with cluster defaults.
1332 @type instance: L{objects.Instance}
1333 @param instance: the instance parameter to fill
1335 @return: a copy of the instance's beparams with missing keys filled from
1336 the cluster defaults
1339 return self.SimpleFillBE(instance.beparams)
1341 def SimpleFillNIC(self, nicparams):
1342 """Fill a given nicparams dict with cluster defaults.
1344 @type nicparams: dict
1345 @param nicparams: the dict to fill
1347 @return: a copy of the passed in nicparams with missing keys filled
1348 from the cluster defaults
1351 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1353 def SimpleFillOS(self, os_name, os_params):
1354 """Fill an instance's osparams dict with cluster defaults.
1356 @type os_name: string
1357 @param os_name: the OS name to use
1358 @type os_params: dict
1359 @param os_params: the dict to fill with default values
1361 @return: a copy of the instance's osparams with missing keys filled from
1362 the cluster defaults
1365 name_only = os_name.split("+", 1)[0]
1367 result = self.osparams.get(name_only, {})
1369 result = FillDict(result, self.osparams.get(os_name, {}))
1371 return FillDict(result, os_params)
1373 def FillND(self, node, nodegroup):
1374 """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1376 @type node: L{objects.Node}
1377 @param node: A Node object to fill
1378 @type nodegroup: L{objects.NodeGroup}
1379 @param nodegroup: A Node object to fill
1380 @return a copy of the node's ndparams with defaults filled
1383 return self.SimpleFillND(nodegroup.FillND(node))
1385 def SimpleFillND(self, ndparams):
1386 """Fill a given ndparams dict with defaults.
1388 @type ndparams: dict
1389 @param ndparams: the dict to fill
1391 @return: a copy of the passed in ndparams with missing keys filled
1392 from the cluster defaults
1395 return FillDict(self.ndparams, ndparams)
1398 class BlockDevStatus(ConfigObject):
1399 """Config object representing the status of a block device."""
1411 class ImportExportStatus(ConfigObject):
1412 """Config object representing the status of an import or export."""
1418 "progress_throughput",
1426 class ImportExportOptions(ConfigObject):
1427 """Options for import/export daemon
1429 @ivar key_name: X509 key name (None for cluster certificate)
1430 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1431 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1432 @ivar magic: Used to ensure the connection goes to the right disk
1433 @ivar ipv6: Whether to use IPv6
1434 @ivar connect_timeout: Number of seconds for establishing connection
1447 class ConfdRequest(ConfigObject):
1448 """Object holding a confd request.
1450 @ivar protocol: confd protocol version
1451 @ivar type: confd query type
1452 @ivar query: query request
1453 @ivar rsalt: requested reply salt
1464 class ConfdReply(ConfigObject):
1465 """Object holding a confd reply.
1467 @ivar protocol: confd protocol version
1468 @ivar status: reply status code (ok, error)
1469 @ivar answer: confd query reply
1470 @ivar serial: configuration serial number
1481 class QueryFieldDefinition(ConfigObject):
1482 """Object holding a query field definition.
1484 @ivar name: Field name
1485 @ivar title: Human-readable title
1486 @ivar kind: Field type
1487 @ivar doc: Human-readable description
1498 class _QueryResponseBase(ConfigObject):
1504 """Custom function for serializing.
1507 mydict = super(_QueryResponseBase, self).ToDict()
1508 mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1512 def FromDict(cls, val):
1513 """Custom function for de-serializing.
1516 obj = super(_QueryResponseBase, cls).FromDict(val)
1517 obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1521 class QueryRequest(ConfigObject):
1522 """Object holding a query request.
1532 class QueryResponse(_QueryResponseBase):
1533 """Object holding the response to a query.
1535 @ivar fields: List of L{QueryFieldDefinition} objects
1536 @ivar data: Requested data
1544 class QueryFieldsRequest(ConfigObject):
1545 """Object holding a request for querying available fields.
1554 class QueryFieldsResponse(_QueryResponseBase):
1555 """Object holding the response to a query for fields.
1557 @ivar fields: List of L{QueryFieldDefinition} objects
1564 class MigrationStatus(ConfigObject):
1565 """Object holding the status of a migration.
1575 class InstanceConsole(ConfigObject):
1576 """Object describing how to access the console of an instance.
1591 """Validates contents of this object.
1594 assert self.kind in constants.CONS_ALL, "Unknown console type"
1595 assert self.instance, "Missing instance name"
1596 assert self.message or self.kind in [constants.CONS_SSH,
1597 constants.CONS_SPICE,
1599 assert self.host or self.kind == constants.CONS_MESSAGE
1600 assert self.port or self.kind in [constants.CONS_MESSAGE,
1602 assert self.user or self.kind in [constants.CONS_MESSAGE,
1603 constants.CONS_SPICE,
1605 assert self.command or self.kind in [constants.CONS_MESSAGE,
1606 constants.CONS_SPICE,
1608 assert self.display or self.kind in [constants.CONS_MESSAGE,
1609 constants.CONS_SPICE,
1614 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1615 """Simple wrapper over ConfigParse that allows serialization.
1617 This class is basically ConfigParser.SafeConfigParser with two
1618 additional methods that allow it to serialize/unserialize to/from a
1623 """Dump this instance and return the string representation."""
1626 return buf.getvalue()
1629 def Loads(cls, data):
1630 """Load data from a string."""
1631 buf = StringIO(data)