4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
47 from ganeti import utils
49 from socket import AF_INET
52 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
53 "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
55 _TIMESTAMPS = ["ctime", "mtime"]
58 # constants used to create InstancePolicy dictionary
59 TISPECS_GROUP_TYPES = {
60 constants.ISPECS_MIN: constants.VTYPE_INT,
61 constants.ISPECS_MAX: constants.VTYPE_INT,
64 TISPECS_CLUSTER_TYPES = {
65 constants.ISPECS_MIN: constants.VTYPE_INT,
66 constants.ISPECS_MAX: constants.VTYPE_INT,
67 constants.ISPECS_STD: constants.VTYPE_INT,
71 def FillDict(defaults_dict, custom_dict, skip_keys=None):
72 """Basic function to apply settings on top a default dict.
74 @type defaults_dict: dict
75 @param defaults_dict: dictionary holding the default values
76 @type custom_dict: dict
77 @param custom_dict: dictionary holding customized value
79 @param skip_keys: which keys not to fill
81 @return: dict with the 'full' values
84 ret_dict = copy.deepcopy(defaults_dict)
85 ret_dict.update(custom_dict)
95 def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None):
96 """Fills an instance policy with defaults.
99 assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
101 for key in constants.IPOLICY_PARAMETERS:
102 ret_dict[key] = FillDict(default_ipolicy[key],
103 custom_ipolicy.get(key, {}),
106 for key in [constants.ISPECS_DTS]:
107 ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
112 def UpgradeGroupedParams(target, defaults):
113 """Update all groups for the target parameter.
115 @type target: dict of dicts
116 @param target: {group: {parameter: value}}
118 @param defaults: default parameter values
122 target = {constants.PP_DEFAULT: defaults}
125 target[group] = FillDict(defaults, target[group])
129 def UpgradeBeParams(target):
130 """Update the be parameters dict to the new format.
133 @param target: "be" parameters dict
136 if constants.BE_MEMORY in target:
137 memory = target[constants.BE_MEMORY]
138 target[constants.BE_MAXMEM] = memory
139 target[constants.BE_MINMEM] = memory
140 del target[constants.BE_MEMORY]
143 def UpgradeDiskParams(diskparams):
144 """Upgrade the disk parameters.
146 @type diskparams: dict
147 @param diskparams: disk parameters to upgrade
149 @return: the upgraded disk parameters dit
153 if diskparams is None:
154 result = constants.DISK_DT_DEFAULTS.copy()
156 # Update the disk parameter values for each disk template.
157 # The code iterates over constants.DISK_TEMPLATES because new templates
158 # might have been added.
159 for template in constants.DISK_TEMPLATES:
160 if template not in diskparams:
161 result[template] = constants.DISK_DT_DEFAULTS[template].copy()
163 result[template] = FillDict(constants.DISK_DT_DEFAULTS[template],
164 diskparams[template])
169 def MakeEmptyIPolicy():
170 """Create empty IPolicy dictionary.
174 (constants.ISPECS_MIN, {}),
175 (constants.ISPECS_MAX, {}),
176 (constants.ISPECS_STD, {}),
180 def CreateIPolicyFromOpts(ispecs_mem_size=None,
181 ispecs_cpu_count=None,
182 ispecs_disk_count=None,
183 ispecs_disk_size=None,
184 ispecs_nic_count=None,
185 ispecs_disk_templates=None,
189 """Creation of instance policy based on command line options.
191 @param fill_all: whether for cluster policies we should ensure that
192 all values are filled
196 # prepare ipolicy dict
197 ipolicy_transposed = {
198 constants.ISPEC_MEM_SIZE: ispecs_mem_size,
199 constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
200 constants.ISPEC_DISK_COUNT: ispecs_disk_count,
201 constants.ISPEC_DISK_SIZE: ispecs_disk_size,
202 constants.ISPEC_NIC_COUNT: ispecs_nic_count,
205 # first, check that the values given are correct
207 forced_type = TISPECS_GROUP_TYPES
209 forced_type = TISPECS_CLUSTER_TYPES
211 for specs in ipolicy_transposed.values():
212 utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
215 ipolicy_out = MakeEmptyIPolicy()
216 for name, specs in ipolicy_transposed.iteritems():
217 assert name in constants.ISPECS_PARAMETERS
218 for key, val in specs.items(): # {min: .. ,max: .., std: ..}
219 ipolicy_out[key][name] = val
221 # no filldict for lists
222 if not group_ipolicy and fill_all and ispecs_disk_templates is None:
223 ispecs_disk_templates = constants.DISK_TEMPLATES
224 if ispecs_disk_templates is not None:
225 ipolicy_out[constants.ISPECS_DTS] = list(ispecs_disk_templates)
230 class ConfigObject(object):
231 """A generic config object.
233 It has the following properties:
235 - provides somewhat safe recursive unpickling and pickling for its classes
236 - unset attributes which are defined in slots are always returned
237 as None instead of raising an error
239 Classes derived from this must always declare __slots__ (we use many
240 config objects and the memory reduction is useful)
245 def __init__(self, **kwargs):
246 for k, v in kwargs.iteritems():
249 def __getattr__(self, name):
250 if name not in self._all_slots():
251 raise AttributeError("Invalid object attribute %s.%s" %
252 (type(self).__name__, name))
255 def __setstate__(self, state):
256 slots = self._all_slots()
259 setattr(self, name, state[name])
263 """Compute the list of all declared slots for a class.
267 for parent in cls.__mro__:
268 slots.extend(getattr(parent, "__slots__", []))
272 """Convert to a dict holding only standard python types.
274 The generic routine just dumps all of this object's attributes in
275 a dict. It does not work if the class has children who are
276 ConfigObjects themselves (e.g. the nics list in an Instance), in
277 which case the object should subclass the function in order to
278 make sure all objects returned are only standard python types.
282 for name in self._all_slots():
283 value = getattr(self, name, None)
284 if value is not None:
288 __getstate__ = ToDict
291 def FromDict(cls, val):
292 """Create an object from a dictionary.
294 This generic routine takes a dict, instantiates a new instance of
295 the given class, and sets attributes based on the dict content.
297 As for `ToDict`, this does not work if the class has children
298 who are ConfigObjects themselves (e.g. the nics list in an
299 Instance), in which case the object should subclass the function
300 and alter the objects.
303 if not isinstance(val, dict):
304 raise errors.ConfigurationError("Invalid object passed to FromDict:"
305 " expected dict, got %s" % type(val))
306 val_str = dict([(str(k), v) for k, v in val.iteritems()])
307 obj = cls(**val_str) # pylint: disable=W0142
311 def _ContainerToDicts(container):
312 """Convert the elements of a container to standard python types.
314 This method converts a container with elements derived from
315 ConfigData to standard python types. If the container is a dict,
316 we don't touch the keys, only the values.
319 if isinstance(container, dict):
320 ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
321 elif isinstance(container, (list, tuple, set, frozenset)):
322 ret = [elem.ToDict() for elem in container]
324 raise TypeError("Invalid type %s passed to _ContainerToDicts" %
329 def _ContainerFromDicts(source, c_type, e_type):
330 """Convert a container from standard python types.
332 This method converts a container with standard python types to
333 ConfigData objects. If the container is a dict, we don't touch the
334 keys, only the values.
337 if not isinstance(c_type, type):
338 raise TypeError("Container type %s passed to _ContainerFromDicts is"
339 " not a type" % type(c_type))
343 ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
344 elif c_type in (list, tuple, set, frozenset):
345 ret = c_type([e_type.FromDict(elem) for elem in source])
347 raise TypeError("Invalid container type %s passed to"
348 " _ContainerFromDicts" % c_type)
352 """Makes a deep copy of the current object and its children.
355 dict_form = self.ToDict()
356 clone_obj = self.__class__.FromDict(dict_form)
360 """Implement __repr__ for ConfigObjects."""
361 return repr(self.ToDict())
363 def UpgradeConfig(self):
364 """Fill defaults for missing configuration values.
366 This method will be called at configuration load time, and its
367 implementation will be object dependent.
373 class TaggableObject(ConfigObject):
374 """An generic class supporting tags.
378 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
381 def ValidateTag(cls, tag):
382 """Check if a tag is valid.
384 If the tag is invalid, an errors.TagError will be raised. The
385 function has no return value.
388 if not isinstance(tag, basestring):
389 raise errors.TagError("Invalid tag type (not a string)")
390 if len(tag) > constants.MAX_TAG_LEN:
391 raise errors.TagError("Tag too long (>%d characters)" %
392 constants.MAX_TAG_LEN)
394 raise errors.TagError("Tags cannot be empty")
395 if not cls.VALID_TAG_RE.match(tag):
396 raise errors.TagError("Tag contains invalid characters")
399 """Return the tags list.
402 tags = getattr(self, "tags", None)
404 tags = self.tags = set()
407 def AddTag(self, tag):
411 self.ValidateTag(tag)
412 tags = self.GetTags()
413 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
414 raise errors.TagError("Too many tags")
415 self.GetTags().add(tag)
417 def RemoveTag(self, tag):
421 self.ValidateTag(tag)
422 tags = self.GetTags()
426 raise errors.TagError("Tag not found")
429 """Taggable-object-specific conversion to standard python types.
431 This replaces the tags set with a list.
434 bo = super(TaggableObject, self).ToDict()
436 tags = bo.get("tags", None)
437 if isinstance(tags, set):
438 bo["tags"] = list(tags)
442 def FromDict(cls, val):
443 """Custom function for instances.
446 obj = super(TaggableObject, cls).FromDict(val)
447 if hasattr(obj, "tags") and isinstance(obj.tags, list):
448 obj.tags = set(obj.tags)
452 class MasterNetworkParameters(ConfigObject):
453 """Network configuration parameters for the master
455 @ivar name: master name
457 @ivar netmask: master netmask
458 @ivar netdev: master network device
459 @ivar ip_family: master IP family
471 class ConfigData(ConfigObject):
472 """Top-level config object."""
483 """Custom function for top-level config data.
485 This just replaces the list of instances, nodes and the cluster
486 with standard python types.
489 mydict = super(ConfigData, self).ToDict()
490 mydict["cluster"] = mydict["cluster"].ToDict()
491 for key in "nodes", "instances", "nodegroups":
492 mydict[key] = self._ContainerToDicts(mydict[key])
497 def FromDict(cls, val):
498 """Custom function for top-level config data
501 obj = super(ConfigData, cls).FromDict(val)
502 obj.cluster = Cluster.FromDict(obj.cluster)
503 obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
504 obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
505 obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
508 def HasAnyDiskOfType(self, dev_type):
509 """Check if in there is at disk of the given type in the configuration.
511 @type dev_type: L{constants.LDS_BLOCK}
512 @param dev_type: the type to look for
514 @return: boolean indicating if a disk of the given type was found or not
517 for instance in self.instances.values():
518 for disk in instance.disks:
519 if disk.IsBasedOnDiskType(dev_type):
523 def UpgradeConfig(self):
524 """Fill defaults for missing configuration values.
527 self.cluster.UpgradeConfig()
528 for node in self.nodes.values():
530 for instance in self.instances.values():
531 instance.UpgradeConfig()
532 if self.nodegroups is None:
534 for nodegroup in self.nodegroups.values():
535 nodegroup.UpgradeConfig()
536 if self.cluster.drbd_usermode_helper is None:
537 # To decide if we set an helper let's check if at least one instance has
538 # a DRBD disk. This does not cover all the possible scenarios but it
539 # gives a good approximation.
540 if self.HasAnyDiskOfType(constants.LD_DRBD8):
541 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
544 class NIC(ConfigObject):
545 """Config object representing a network card."""
546 __slots__ = ["mac", "ip", "nicparams"]
549 def CheckParameterSyntax(cls, nicparams):
550 """Check the given parameters for validity.
552 @type nicparams: dict
553 @param nicparams: dictionary with parameter names/value
554 @raise errors.ConfigurationError: when a parameter is not valid
557 if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
558 nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
559 err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
560 raise errors.ConfigurationError(err)
562 if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
563 not nicparams[constants.NIC_LINK]):
564 err = "Missing bridged nic link"
565 raise errors.ConfigurationError(err)
568 class Disk(ConfigObject):
569 """Config object representing a block device."""
570 __slots__ = ["dev_type", "logical_id", "physical_id",
571 "children", "iv_name", "size", "mode", "params"]
573 def CreateOnSecondary(self):
574 """Test if this device needs to be created on a secondary node."""
575 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
577 def AssembleOnSecondary(self):
578 """Test if this device needs to be assembled on a secondary node."""
579 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
581 def OpenOnSecondary(self):
582 """Test if this device needs to be opened on a secondary node."""
583 return self.dev_type in (constants.LD_LV,)
585 def StaticDevPath(self):
586 """Return the device path if this device type has a static one.
588 Some devices (LVM for example) live always at the same /dev/ path,
589 irrespective of their status. For such devices, we return this
590 path, for others we return None.
592 @warning: The path returned is not a normalized pathname; callers
593 should check that it is a valid path.
596 if self.dev_type == constants.LD_LV:
597 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
598 elif self.dev_type == constants.LD_BLOCKDEV:
599 return self.logical_id[1]
602 def ChildrenNeeded(self):
603 """Compute the needed number of children for activation.
605 This method will return either -1 (all children) or a positive
606 number denoting the minimum number of children needed for
607 activation (only mirrored devices will usually return >=0).
609 Currently, only DRBD8 supports diskless activation (therefore we
610 return 0), for all other we keep the previous semantics and return
614 if self.dev_type == constants.LD_DRBD8:
618 def IsBasedOnDiskType(self, dev_type):
619 """Check if the disk or its children are based on the given type.
621 @type dev_type: L{constants.LDS_BLOCK}
622 @param dev_type: the type to look for
624 @return: boolean indicating if a device of the given type was found or not
628 for child in self.children:
629 if child.IsBasedOnDiskType(dev_type):
631 return self.dev_type == dev_type
633 def GetNodes(self, node):
634 """This function returns the nodes this device lives on.
636 Given the node on which the parent of the device lives on (or, in
637 case of a top-level device, the primary node of the devices'
638 instance), this function will return a list of nodes on which this
639 devices needs to (or can) be assembled.
642 if self.dev_type in [constants.LD_LV, constants.LD_FILE,
643 constants.LD_BLOCKDEV]:
645 elif self.dev_type in constants.LDS_DRBD:
646 result = [self.logical_id[0], self.logical_id[1]]
647 if node not in result:
648 raise errors.ConfigurationError("DRBD device passed unknown node")
650 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
653 def ComputeNodeTree(self, parent_node):
654 """Compute the node/disk tree for this disk and its children.
656 This method, given the node on which the parent disk lives, will
657 return the list of all (node, disk) pairs which describe the disk
658 tree in the most compact way. For example, a drbd/lvm stack
659 will be returned as (primary_node, drbd) and (secondary_node, drbd)
660 which represents all the top-level devices on the nodes.
663 my_nodes = self.GetNodes(parent_node)
664 result = [(node, self) for node in my_nodes]
665 if not self.children:
668 for node in my_nodes:
669 for child in self.children:
670 child_result = child.ComputeNodeTree(node)
671 if len(child_result) == 1:
672 # child (and all its descendants) is simple, doesn't split
673 # over multiple hosts, so we don't need to describe it, our
674 # own entry for this node describes it completely
677 # check if child nodes differ from my nodes; note that
678 # subdisk can differ from the child itself, and be instead
679 # one of its descendants
680 for subnode, subdisk in child_result:
681 if subnode not in my_nodes:
682 result.append((subnode, subdisk))
683 # otherwise child is under our own node, so we ignore this
684 # entry (but probably the other results in the list will
688 def ComputeGrowth(self, amount):
689 """Compute the per-VG growth requirements.
691 This only works for VG-based disks.
693 @type amount: integer
694 @param amount: the desired increase in (user-visible) disk space
696 @return: a dictionary of volume-groups and the required size
699 if self.dev_type == constants.LD_LV:
700 return {self.logical_id[0]: amount}
701 elif self.dev_type == constants.LD_DRBD8:
703 return self.children[0].ComputeGrowth(amount)
707 # Other disk types do not require VG space
710 def RecordGrow(self, amount):
711 """Update the size of this disk after growth.
713 This method recurses over the disks's children and updates their
714 size correspondigly. The method needs to be kept in sync with the
715 actual algorithms from bdev.
718 if self.dev_type in (constants.LD_LV, constants.LD_FILE):
720 elif self.dev_type == constants.LD_DRBD8:
722 self.children[0].RecordGrow(amount)
725 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
726 " disk type %s" % self.dev_type)
729 """Sets recursively the size to zero for the disk and its children.
733 for child in self.children:
737 def SetPhysicalID(self, target_node, nodes_ip):
738 """Convert the logical ID to the physical ID.
740 This is used only for drbd, which needs ip/port configuration.
742 The routine descends down and updates its children also, because
743 this helps when the only the top device is passed to the remote
747 - target_node: the node we wish to configure for
748 - nodes_ip: a mapping of node name to ip
750 The target_node must exist in in nodes_ip, and must be one of the
751 nodes in the logical ID for each of the DRBD devices encountered
756 for child in self.children:
757 child.SetPhysicalID(target_node, nodes_ip)
759 if self.logical_id is None and self.physical_id is not None:
761 if self.dev_type in constants.LDS_DRBD:
762 pnode, snode, port, pminor, sminor, secret = self.logical_id
763 if target_node not in (pnode, snode):
764 raise errors.ConfigurationError("DRBD device not knowing node %s" %
766 pnode_ip = nodes_ip.get(pnode, None)
767 snode_ip = nodes_ip.get(snode, None)
768 if pnode_ip is None or snode_ip is None:
769 raise errors.ConfigurationError("Can't find primary or secondary node"
770 " for %s" % str(self))
771 p_data = (pnode_ip, port)
772 s_data = (snode_ip, port)
773 if pnode == target_node:
774 self.physical_id = p_data + s_data + (pminor, secret)
775 else: # it must be secondary, we tested above
776 self.physical_id = s_data + p_data + (sminor, secret)
778 self.physical_id = self.logical_id
782 """Disk-specific conversion to standard python types.
784 This replaces the children lists of objects with lists of
785 standard python types.
788 bo = super(Disk, self).ToDict()
790 for attr in ("children",):
791 alist = bo.get(attr, None)
793 bo[attr] = self._ContainerToDicts(alist)
797 def FromDict(cls, val):
798 """Custom function for Disks
801 obj = super(Disk, cls).FromDict(val)
803 obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
804 if obj.logical_id and isinstance(obj.logical_id, list):
805 obj.logical_id = tuple(obj.logical_id)
806 if obj.physical_id and isinstance(obj.physical_id, list):
807 obj.physical_id = tuple(obj.physical_id)
808 if obj.dev_type in constants.LDS_DRBD:
809 # we need a tuple of length six here
810 if len(obj.logical_id) < 6:
811 obj.logical_id += (None,) * (6 - len(obj.logical_id))
815 """Custom str() formatter for disks.
818 if self.dev_type == constants.LD_LV:
819 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
820 elif self.dev_type in constants.LDS_DRBD:
821 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
823 if self.physical_id is None:
826 phy = ("configured as %s:%s %s:%s" %
827 (self.physical_id[0], self.physical_id[1],
828 self.physical_id[2], self.physical_id[3]))
830 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
831 (node_a, minor_a, node_b, minor_b, port, phy))
832 if self.children and self.children.count(None) == 0:
833 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
835 val += "no local storage"
837 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
838 (self.dev_type, self.logical_id, self.physical_id, self.children))
839 if self.iv_name is None:
840 val += ", not visible"
842 val += ", visible as /dev/%s" % self.iv_name
843 if isinstance(self.size, int):
844 val += ", size=%dm)>" % self.size
846 val += ", size='%s')>" % (self.size,)
850 """Checks that this disk is correctly configured.
854 if self.mode not in constants.DISK_ACCESS_SET:
855 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
858 def UpgradeConfig(self):
859 """Fill defaults for missing configuration values.
863 for child in self.children:
864 child.UpgradeConfig()
867 self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
869 self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
871 # add here config upgrade for this disk
874 class InstancePolicy(ConfigObject):
875 """Config object representing instance policy limits dictionary."""
876 __slots__ = ["min", "max", "std", "disk_templates"]
879 def CheckParameterSyntax(cls, ipolicy):
880 """ Check the instance policy for validity.
883 for param in constants.ISPECS_PARAMETERS:
884 InstancePolicy.CheckISpecSyntax(ipolicy, param)
885 if constants.ISPECS_DTS in ipolicy:
886 InstancePolicy.CheckDiskTemplates(ipolicy[constants.ISPECS_DTS])
889 def CheckISpecSyntax(cls, ipolicy, name):
890 """Check the instance policy for validity on a given key.
892 We check if the instance policy makes sense for a given key, that is
893 if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
896 @param ipolicy: dictionary with min, max, std specs
898 @param name: what are the limits for
899 @raise errors.ConfigureError: when specs for given name are not valid
902 min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
903 std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
904 max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
905 err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
907 ipolicy[constants.ISPECS_MIN].get(name, "-"),
908 ipolicy[constants.ISPECS_MAX].get(name, "-"),
909 ipolicy[constants.ISPECS_STD].get(name, "-")))
910 if min_v > std_v or std_v > max_v:
911 raise errors.ConfigurationError(err)
914 def CheckDiskTemplates(cls, disk_templates):
915 """Checks the disk templates for validity.
918 wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
920 raise errors.ConfigurationError("Invalid disk template(s) %s" %
921 utils.CommaJoin(wrong))
924 class Instance(TaggableObject):
925 """Config object representing an instance."""
940 ] + _TIMESTAMPS + _UUID
942 def _ComputeSecondaryNodes(self):
943 """Compute the list of secondary nodes.
945 This is a simple wrapper over _ComputeAllNodes.
948 all_nodes = set(self._ComputeAllNodes())
949 all_nodes.discard(self.primary_node)
950 return tuple(all_nodes)
952 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
953 "List of secondary nodes")
955 def _ComputeAllNodes(self):
956 """Compute the list of all nodes.
958 Since the data is already there (in the drbd disks), keeping it as
959 a separate normal attribute is redundant and if not properly
960 synchronised can cause problems. Thus it's better to compute it
964 def _Helper(nodes, device):
965 """Recursively computes nodes given a top device."""
966 if device.dev_type in constants.LDS_DRBD:
967 nodea, nodeb = device.logical_id[:2]
971 for child in device.children:
972 _Helper(nodes, child)
975 all_nodes.add(self.primary_node)
976 for device in self.disks:
977 _Helper(all_nodes, device)
978 return tuple(all_nodes)
980 all_nodes = property(_ComputeAllNodes, None, None,
981 "List of all nodes of the instance")
983 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
984 """Provide a mapping of nodes to LVs this instance owns.
986 This function figures out what logical volumes should belong on
987 which nodes, recursing through a device tree.
989 @param lvmap: optional dictionary to receive the
990 'node' : ['lv', ...] data.
992 @return: None if lvmap arg is given, otherwise, a dictionary of
993 the form { 'nodename' : ['volume1', 'volume2', ...], ... };
994 volumeN is of the form "vg_name/lv_name", compatible with
999 node = self.primary_node
1007 if not node in lvmap:
1015 if dev.dev_type == constants.LD_LV:
1016 lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1018 elif dev.dev_type in constants.LDS_DRBD:
1020 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1021 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1024 self.MapLVsByNode(lvmap, dev.children, node)
1028 def FindDisk(self, idx):
1029 """Find a disk given having a specified index.
1031 This is just a wrapper that does validation of the index.
1034 @param idx: the disk index
1036 @return: the corresponding disk
1037 @raise errors.OpPrereqError: when the given index is not valid
1042 return self.disks[idx]
1043 except (TypeError, ValueError), err:
1044 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1047 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1048 " 0 to %d" % (idx, len(self.disks) - 1),
1052 """Instance-specific conversion to standard python types.
1054 This replaces the children lists of objects with lists of standard
1058 bo = super(Instance, self).ToDict()
1060 for attr in "nics", "disks":
1061 alist = bo.get(attr, None)
1063 nlist = self._ContainerToDicts(alist)
1070 def FromDict(cls, val):
1071 """Custom function for instances.
1074 if "admin_state" not in val:
1075 if val.get("admin_up", False):
1076 val["admin_state"] = constants.ADMINST_UP
1078 val["admin_state"] = constants.ADMINST_DOWN
1079 if "admin_up" in val:
1081 obj = super(Instance, cls).FromDict(val)
1082 obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1083 obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1086 def UpgradeConfig(self):
1087 """Fill defaults for missing configuration values.
1090 for nic in self.nics:
1092 for disk in self.disks:
1093 disk.UpgradeConfig()
1095 for key in constants.HVC_GLOBALS:
1097 del self.hvparams[key]
1100 if self.osparams is None:
1102 UpgradeBeParams(self.beparams)
1105 class OS(ConfigObject):
1106 """Config object representing an operating system.
1108 @type supported_parameters: list
1109 @ivar supported_parameters: a list of tuples, name and description,
1110 containing the supported parameters by this OS
1112 @type VARIANT_DELIM: string
1113 @cvar VARIANT_DELIM: the variant delimiter
1125 "supported_variants",
1126 "supported_parameters",
1132 def SplitNameVariant(cls, name):
1133 """Splits the name into the proper name and variant.
1135 @param name: the OS (unprocessed) name
1137 @return: a list of two elements; if the original name didn't
1138 contain a variant, it's returned as an empty string
1141 nv = name.split(cls.VARIANT_DELIM, 1)
1147 def GetName(cls, name):
1148 """Returns the proper name of the os (without the variant).
1150 @param name: the OS (unprocessed) name
1153 return cls.SplitNameVariant(name)[0]
1156 def GetVariant(cls, name):
1157 """Returns the variant the os (without the base name).
1159 @param name: the OS (unprocessed) name
1162 return cls.SplitNameVariant(name)[1]
1165 class NodeHvState(ConfigObject):
1166 """Hypvervisor state on a node.
1168 @ivar mem_total: Total amount of memory
1169 @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1171 @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1173 @ivar mem_inst: Memory used by instances living on node
1174 @ivar cpu_total: Total node CPU core count
1175 @ivar cpu_node: Number of CPU cores reserved for the node itself
1188 class NodeDiskState(ConfigObject):
1189 """Disk state on a node.
1199 class Node(TaggableObject):
1200 """Config object representing a node.
1202 @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1203 @ivar hv_state_static: Hypervisor state overriden by user
1204 @ivar disk_state: Disk state (e.g. free space)
1205 @ivar disk_state_static: Disk state overriden by user
1224 "disk_state_static",
1225 ] + _TIMESTAMPS + _UUID
1227 def UpgradeConfig(self):
1228 """Fill defaults for missing configuration values.
1231 # pylint: disable=E0203
1232 # because these are "defined" via slots, not manually
1233 if self.master_capable is None:
1234 self.master_capable = True
1236 if self.vm_capable is None:
1237 self.vm_capable = True
1239 if self.ndparams is None:
1242 if self.powered is None:
1246 """Custom function for serializing.
1249 data = super(Node, self).ToDict()
1251 hv_state = data.get("hv_state", None)
1252 if hv_state is not None:
1253 data["hv_state"] = self._ContainerToDicts(hv_state)
1255 disk_state = data.get("disk_state", None)
1256 if disk_state is not None:
1257 data["disk_state"] = \
1258 dict((key, self._ContainerToDicts(value))
1259 for (key, value) in disk_state.items())
1264 def FromDict(cls, val):
1265 """Custom function for deserializing.
1268 obj = super(Node, cls).FromDict(val)
1270 if obj.hv_state is not None:
1271 obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1273 if obj.disk_state is not None:
1275 dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1276 for (key, value) in obj.disk_state.items())
1281 class NodeGroup(TaggableObject):
1282 """Config object representing a node group."""
1291 "disk_state_static",
1293 ] + _TIMESTAMPS + _UUID
1296 """Custom function for nodegroup.
1298 This discards the members object, which gets recalculated and is only kept
1302 mydict = super(NodeGroup, self).ToDict()
1303 del mydict["members"]
1307 def FromDict(cls, val):
1308 """Custom function for nodegroup.
1310 The members slot is initialized to an empty list, upon deserialization.
1313 obj = super(NodeGroup, cls).FromDict(val)
1317 def UpgradeConfig(self):
1318 """Fill defaults for missing configuration values.
1321 if self.ndparams is None:
1324 if self.serial_no is None:
1327 if self.alloc_policy is None:
1328 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1330 # We only update mtime, and not ctime, since we would not be able
1331 # to provide a correct value for creation time.
1332 if self.mtime is None:
1333 self.mtime = time.time()
1335 self.diskparams = UpgradeDiskParams(self.diskparams)
1336 if self.ipolicy is None:
1337 self.ipolicy = MakeEmptyIPolicy()
1339 def FillND(self, node):
1340 """Return filled out ndparams for L{objects.Node}
1342 @type node: L{objects.Node}
1343 @param node: A Node object to fill
1344 @return a copy of the node's ndparams with defaults filled
1347 return self.SimpleFillND(node.ndparams)
1349 def SimpleFillND(self, ndparams):
1350 """Fill a given ndparams dict with defaults.
1352 @type ndparams: dict
1353 @param ndparams: the dict to fill
1355 @return: a copy of the passed in ndparams with missing keys filled
1356 from the node group defaults
1359 return FillDict(self.ndparams, ndparams)
1362 class Cluster(TaggableObject):
1363 """Config object representing the cluster."""
1367 "highest_used_port",
1370 "volume_group_name",
1372 "drbd_usermode_helper",
1374 "default_hypervisor",
1379 "use_external_mip_script",
1382 "shared_file_storage_dir",
1383 "enabled_hypervisors",
1392 "candidate_pool_size",
1395 "maintain_node_health",
1397 "default_iallocator",
1400 "primary_ip_family",
1401 "prealloc_wipe_disks",
1403 "disk_state_static",
1404 ] + _TIMESTAMPS + _UUID
1406 def UpgradeConfig(self):
1407 """Fill defaults for missing configuration values.
1410 # pylint: disable=E0203
1411 # because these are "defined" via slots, not manually
1412 if self.hvparams is None:
1413 self.hvparams = constants.HVC_DEFAULTS
1415 for hypervisor in self.hvparams:
1416 self.hvparams[hypervisor] = FillDict(
1417 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1419 if self.os_hvp is None:
1422 # osparams added before 2.2
1423 if self.osparams is None:
1426 if self.ndparams is None:
1427 self.ndparams = constants.NDC_DEFAULTS
1429 self.beparams = UpgradeGroupedParams(self.beparams,
1430 constants.BEC_DEFAULTS)
1431 for beparams_group in self.beparams:
1432 UpgradeBeParams(self.beparams[beparams_group])
1434 migrate_default_bridge = not self.nicparams
1435 self.nicparams = UpgradeGroupedParams(self.nicparams,
1436 constants.NICC_DEFAULTS)
1437 if migrate_default_bridge:
1438 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1441 if self.modify_etc_hosts is None:
1442 self.modify_etc_hosts = True
1444 if self.modify_ssh_setup is None:
1445 self.modify_ssh_setup = True
1447 # default_bridge is no longer used in 2.1. The slot is left there to
1448 # support auto-upgrading. It can be removed once we decide to deprecate
1449 # upgrading straight from 2.0.
1450 if self.default_bridge is not None:
1451 self.default_bridge = None
1453 # default_hypervisor is just the first enabled one in 2.1. This slot and
1454 # code can be removed once upgrading straight from 2.0 is deprecated.
1455 if self.default_hypervisor is not None:
1456 self.enabled_hypervisors = ([self.default_hypervisor] +
1457 [hvname for hvname in self.enabled_hypervisors
1458 if hvname != self.default_hypervisor])
1459 self.default_hypervisor = None
1461 # maintain_node_health added after 2.1.1
1462 if self.maintain_node_health is None:
1463 self.maintain_node_health = False
1465 if self.uid_pool is None:
1468 if self.default_iallocator is None:
1469 self.default_iallocator = ""
1471 # reserved_lvs added before 2.2
1472 if self.reserved_lvs is None:
1473 self.reserved_lvs = []
1475 # hidden and blacklisted operating systems added before 2.2.1
1476 if self.hidden_os is None:
1479 if self.blacklisted_os is None:
1480 self.blacklisted_os = []
1482 # primary_ip_family added before 2.3
1483 if self.primary_ip_family is None:
1484 self.primary_ip_family = AF_INET
1486 if self.master_netmask is None:
1487 ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1488 self.master_netmask = ipcls.iplen
1490 if self.prealloc_wipe_disks is None:
1491 self.prealloc_wipe_disks = False
1493 # shared_file_storage_dir added before 2.5
1494 if self.shared_file_storage_dir is None:
1495 self.shared_file_storage_dir = ""
1497 if self.use_external_mip_script is None:
1498 self.use_external_mip_script = False
1500 self.diskparams = UpgradeDiskParams(self.diskparams)
1502 # instance policy added before 2.6
1503 if self.ipolicy is None:
1504 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1507 def primary_hypervisor(self):
1508 """The first hypervisor is the primary.
1510 Useful, for example, for L{Node}'s hv/disk state.
1513 return self.enabled_hypervisors[0]
1516 """Custom function for cluster.
1519 mydict = super(Cluster, self).ToDict()
1520 mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1524 def FromDict(cls, val):
1525 """Custom function for cluster.
1528 obj = super(Cluster, cls).FromDict(val)
1529 if not isinstance(obj.tcpudp_port_pool, set):
1530 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1533 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1534 """Get the default hypervisor parameters for the cluster.
1536 @param hypervisor: the hypervisor name
1537 @param os_name: if specified, we'll also update the defaults for this OS
1538 @param skip_keys: if passed, list of keys not to use
1539 @return: the defaults dict
1542 if skip_keys is None:
1545 fill_stack = [self.hvparams.get(hypervisor, {})]
1546 if os_name is not None:
1547 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1548 fill_stack.append(os_hvp)
1551 for o_dict in fill_stack:
1552 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1556 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1557 """Fill a given hvparams dict with cluster defaults.
1559 @type hv_name: string
1560 @param hv_name: the hypervisor to use
1561 @type os_name: string
1562 @param os_name: the OS to use for overriding the hypervisor defaults
1563 @type skip_globals: boolean
1564 @param skip_globals: if True, the global hypervisor parameters will
1567 @return: a copy of the given hvparams with missing keys filled from
1568 the cluster defaults
1572 skip_keys = constants.HVC_GLOBALS
1576 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1577 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1579 def FillHV(self, instance, skip_globals=False):
1580 """Fill an instance's hvparams dict with cluster defaults.
1582 @type instance: L{objects.Instance}
1583 @param instance: the instance parameter to fill
1584 @type skip_globals: boolean
1585 @param skip_globals: if True, the global hypervisor parameters will
1588 @return: a copy of the instance's hvparams with missing keys filled from
1589 the cluster defaults
1592 return self.SimpleFillHV(instance.hypervisor, instance.os,
1593 instance.hvparams, skip_globals)
1595 def SimpleFillBE(self, beparams):
1596 """Fill a given beparams dict with cluster defaults.
1598 @type beparams: dict
1599 @param beparams: the dict to fill
1601 @return: a copy of the passed in beparams with missing keys filled
1602 from the cluster defaults
1605 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1607 def FillBE(self, instance):
1608 """Fill an instance's beparams dict with cluster defaults.
1610 @type instance: L{objects.Instance}
1611 @param instance: the instance parameter to fill
1613 @return: a copy of the instance's beparams with missing keys filled from
1614 the cluster defaults
1617 return self.SimpleFillBE(instance.beparams)
1619 def SimpleFillNIC(self, nicparams):
1620 """Fill a given nicparams dict with cluster defaults.
1622 @type nicparams: dict
1623 @param nicparams: the dict to fill
1625 @return: a copy of the passed in nicparams with missing keys filled
1626 from the cluster defaults
1629 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1631 def SimpleFillOS(self, os_name, os_params):
1632 """Fill an instance's osparams dict with cluster defaults.
1634 @type os_name: string
1635 @param os_name: the OS name to use
1636 @type os_params: dict
1637 @param os_params: the dict to fill with default values
1639 @return: a copy of the instance's osparams with missing keys filled from
1640 the cluster defaults
1643 name_only = os_name.split("+", 1)[0]
1645 result = self.osparams.get(name_only, {})
1647 result = FillDict(result, self.osparams.get(os_name, {}))
1649 return FillDict(result, os_params)
1652 def SimpleFillHvState(hv_state):
1653 """Fill an hv_state sub dict with cluster defaults.
1656 return FillDict(constants.HVST_DEFAULTS, hv_state)
1659 def SimpleFillDiskState(disk_state):
1660 """Fill an disk_state sub dict with cluster defaults.
1663 return FillDict(constants.DS_DEFAULTS, disk_state)
1665 def FillND(self, node, nodegroup):
1666 """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1668 @type node: L{objects.Node}
1669 @param node: A Node object to fill
1670 @type nodegroup: L{objects.NodeGroup}
1671 @param nodegroup: A Node object to fill
1672 @return a copy of the node's ndparams with defaults filled
1675 return self.SimpleFillND(nodegroup.FillND(node))
1677 def SimpleFillND(self, ndparams):
1678 """Fill a given ndparams dict with defaults.
1680 @type ndparams: dict
1681 @param ndparams: the dict to fill
1683 @return: a copy of the passed in ndparams with missing keys filled
1684 from the cluster defaults
1687 return FillDict(self.ndparams, ndparams)
1689 def SimpleFillIPolicy(self, ipolicy):
1690 """ Fill instance policy dict with defaults.
1693 @param ipolicy: the dict to fill
1695 @return: a copy of passed ipolicy with missing keys filled from
1696 the cluster defaults
1699 return FillIPolicy(self.ipolicy, ipolicy)
1702 class BlockDevStatus(ConfigObject):
1703 """Config object representing the status of a block device."""
1715 class ImportExportStatus(ConfigObject):
1716 """Config object representing the status of an import or export."""
1722 "progress_throughput",
1730 class ImportExportOptions(ConfigObject):
1731 """Options for import/export daemon
1733 @ivar key_name: X509 key name (None for cluster certificate)
1734 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1735 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1736 @ivar magic: Used to ensure the connection goes to the right disk
1737 @ivar ipv6: Whether to use IPv6
1738 @ivar connect_timeout: Number of seconds for establishing connection
1751 class ConfdRequest(ConfigObject):
1752 """Object holding a confd request.
1754 @ivar protocol: confd protocol version
1755 @ivar type: confd query type
1756 @ivar query: query request
1757 @ivar rsalt: requested reply salt
1768 class ConfdReply(ConfigObject):
1769 """Object holding a confd reply.
1771 @ivar protocol: confd protocol version
1772 @ivar status: reply status code (ok, error)
1773 @ivar answer: confd query reply
1774 @ivar serial: configuration serial number
1785 class QueryFieldDefinition(ConfigObject):
1786 """Object holding a query field definition.
1788 @ivar name: Field name
1789 @ivar title: Human-readable title
1790 @ivar kind: Field type
1791 @ivar doc: Human-readable description
1802 class _QueryResponseBase(ConfigObject):
1808 """Custom function for serializing.
1811 mydict = super(_QueryResponseBase, self).ToDict()
1812 mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1816 def FromDict(cls, val):
1817 """Custom function for de-serializing.
1820 obj = super(_QueryResponseBase, cls).FromDict(val)
1821 obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1825 class QueryRequest(ConfigObject):
1826 """Object holding a query request.
1836 class QueryResponse(_QueryResponseBase):
1837 """Object holding the response to a query.
1839 @ivar fields: List of L{QueryFieldDefinition} objects
1840 @ivar data: Requested data
1848 class QueryFieldsRequest(ConfigObject):
1849 """Object holding a request for querying available fields.
1858 class QueryFieldsResponse(_QueryResponseBase):
1859 """Object holding the response to a query for fields.
1861 @ivar fields: List of L{QueryFieldDefinition} objects
1868 class MigrationStatus(ConfigObject):
1869 """Object holding the status of a migration.
1879 class InstanceConsole(ConfigObject):
1880 """Object describing how to access the console of an instance.
1895 """Validates contents of this object.
1898 assert self.kind in constants.CONS_ALL, "Unknown console type"
1899 assert self.instance, "Missing instance name"
1900 assert self.message or self.kind in [constants.CONS_SSH,
1901 constants.CONS_SPICE,
1903 assert self.host or self.kind == constants.CONS_MESSAGE
1904 assert self.port or self.kind in [constants.CONS_MESSAGE,
1906 assert self.user or self.kind in [constants.CONS_MESSAGE,
1907 constants.CONS_SPICE,
1909 assert self.command or self.kind in [constants.CONS_MESSAGE,
1910 constants.CONS_SPICE,
1912 assert self.display or self.kind in [constants.CONS_MESSAGE,
1913 constants.CONS_SPICE,
1918 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1919 """Simple wrapper over ConfigParse that allows serialization.
1921 This class is basically ConfigParser.SafeConfigParser with two
1922 additional methods that allow it to serialize/unserialize to/from a
1927 """Dump this instance and return the string representation."""
1930 return buf.getvalue()
1933 def Loads(cls, data):
1934 """Load data from a string."""
1935 buf = StringIO(data)