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_ISPECS:
102 ret_dict[key] = FillDict(default_ipolicy[key],
103 custom_ipolicy.get(key, {}),
106 for key in [constants.IPOLICY_DTS]:
107 ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
108 # other items which we know we can directly copy (immutables)
109 for key in constants.IPOLICY_PARAMETERS:
110 ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
115 def UpgradeGroupedParams(target, defaults):
116 """Update all groups for the target parameter.
118 @type target: dict of dicts
119 @param target: {group: {parameter: value}}
121 @param defaults: default parameter values
125 target = {constants.PP_DEFAULT: defaults}
128 target[group] = FillDict(defaults, target[group])
132 def UpgradeBeParams(target):
133 """Update the be parameters dict to the new format.
136 @param target: "be" parameters dict
139 if constants.BE_MEMORY in target:
140 memory = target[constants.BE_MEMORY]
141 target[constants.BE_MAXMEM] = memory
142 target[constants.BE_MINMEM] = memory
143 del target[constants.BE_MEMORY]
146 def UpgradeDiskParams(diskparams):
147 """Upgrade the disk parameters.
149 @type diskparams: dict
150 @param diskparams: disk parameters to upgrade
152 @return: the upgraded disk parameters dict
155 if diskparams is None:
156 result = constants.DISK_DT_DEFAULTS.copy()
158 # Update the disk parameter values for each disk template.
159 # The code iterates over constants.DISK_TEMPLATES because new templates
160 # might have been added.
161 result = dict((dt, FillDict(constants.DISK_DT_DEFAULTS[dt],
162 diskparams.get(dt, {})))
163 for dt in constants.DISK_TEMPLATES)
168 def UpgradeNDParams(ndparams):
169 """Upgrade ndparams structure.
172 @param ndparams: disk parameters to upgrade
174 @return: the upgraded node parameters dict
180 return FillDict(constants.NDC_DEFAULTS, ndparams)
183 def MakeEmptyIPolicy():
184 """Create empty IPolicy dictionary.
188 (constants.ISPECS_MIN, {}),
189 (constants.ISPECS_MAX, {}),
190 (constants.ISPECS_STD, {}),
194 def CreateIPolicyFromOpts(ispecs_mem_size=None,
195 ispecs_cpu_count=None,
196 ispecs_disk_count=None,
197 ispecs_disk_size=None,
198 ispecs_nic_count=None,
199 ipolicy_disk_templates=None,
200 ipolicy_vcpu_ratio=None,
204 """Creation of instance policy based on command line options.
206 @param fill_all: whether for cluster policies we should ensure that
207 all values are filled
211 # prepare ipolicy dict
212 ipolicy_transposed = {
213 constants.ISPEC_MEM_SIZE: ispecs_mem_size,
214 constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
215 constants.ISPEC_DISK_COUNT: ispecs_disk_count,
216 constants.ISPEC_DISK_SIZE: ispecs_disk_size,
217 constants.ISPEC_NIC_COUNT: ispecs_nic_count,
220 # first, check that the values given are correct
222 forced_type = TISPECS_GROUP_TYPES
224 forced_type = TISPECS_CLUSTER_TYPES
226 for specs in ipolicy_transposed.values():
227 utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
230 ipolicy_out = MakeEmptyIPolicy()
231 for name, specs in ipolicy_transposed.iteritems():
232 assert name in constants.ISPECS_PARAMETERS
233 for key, val in specs.items(): # {min: .. ,max: .., std: ..}
234 ipolicy_out[key][name] = val
236 # no filldict for non-dicts
237 if not group_ipolicy and fill_all:
238 if ipolicy_disk_templates is None:
239 ipolicy_disk_templates = constants.DISK_TEMPLATES
240 if ipolicy_vcpu_ratio is None:
241 ipolicy_vcpu_ratio = \
242 constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
243 if ipolicy_disk_templates is not None:
244 ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
245 if ipolicy_vcpu_ratio is not None:
246 ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
248 assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
253 class ConfigObject(object):
254 """A generic config object.
256 It has the following properties:
258 - provides somewhat safe recursive unpickling and pickling for its classes
259 - unset attributes which are defined in slots are always returned
260 as None instead of raising an error
262 Classes derived from this must always declare __slots__ (we use many
263 config objects and the memory reduction is useful)
268 def __init__(self, **kwargs):
269 for k, v in kwargs.iteritems():
272 def __getattr__(self, name):
273 if name not in self._all_slots():
274 raise AttributeError("Invalid object attribute %s.%s" %
275 (type(self).__name__, name))
278 def __setstate__(self, state):
279 slots = self._all_slots()
282 setattr(self, name, state[name])
286 """Compute the list of all declared slots for a class.
290 for parent in cls.__mro__:
291 slots.extend(getattr(parent, "__slots__", []))
294 #: Public getter for the defined slots
295 GetAllSlots = _all_slots
298 """Convert to a dict holding only standard python types.
300 The generic routine just dumps all of this object's attributes in
301 a dict. It does not work if the class has children who are
302 ConfigObjects themselves (e.g. the nics list in an Instance), in
303 which case the object should subclass the function in order to
304 make sure all objects returned are only standard python types.
308 for name in self._all_slots():
309 value = getattr(self, name, None)
310 if value is not None:
314 __getstate__ = ToDict
317 def FromDict(cls, val):
318 """Create an object from a dictionary.
320 This generic routine takes a dict, instantiates a new instance of
321 the given class, and sets attributes based on the dict content.
323 As for `ToDict`, this does not work if the class has children
324 who are ConfigObjects themselves (e.g. the nics list in an
325 Instance), in which case the object should subclass the function
326 and alter the objects.
329 if not isinstance(val, dict):
330 raise errors.ConfigurationError("Invalid object passed to FromDict:"
331 " expected dict, got %s" % type(val))
332 val_str = dict([(str(k), v) for k, v in val.iteritems()])
333 obj = cls(**val_str) # pylint: disable=W0142
337 def _ContainerToDicts(container):
338 """Convert the elements of a container to standard python types.
340 This method converts a container with elements derived from
341 ConfigData to standard python types. If the container is a dict,
342 we don't touch the keys, only the values.
345 if isinstance(container, dict):
346 ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
347 elif isinstance(container, (list, tuple, set, frozenset)):
348 ret = [elem.ToDict() for elem in container]
350 raise TypeError("Invalid type %s passed to _ContainerToDicts" %
355 def _ContainerFromDicts(source, c_type, e_type):
356 """Convert a container from standard python types.
358 This method converts a container with standard python types to
359 ConfigData objects. If the container is a dict, we don't touch the
360 keys, only the values.
363 if not isinstance(c_type, type):
364 raise TypeError("Container type %s passed to _ContainerFromDicts is"
365 " not a type" % type(c_type))
369 ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
370 elif c_type in (list, tuple, set, frozenset):
371 ret = c_type([e_type.FromDict(elem) for elem in source])
373 raise TypeError("Invalid container type %s passed to"
374 " _ContainerFromDicts" % c_type)
378 """Makes a deep copy of the current object and its children.
381 dict_form = self.ToDict()
382 clone_obj = self.__class__.FromDict(dict_form)
386 """Implement __repr__ for ConfigObjects."""
387 return repr(self.ToDict())
389 def UpgradeConfig(self):
390 """Fill defaults for missing configuration values.
392 This method will be called at configuration load time, and its
393 implementation will be object dependent.
399 class TaggableObject(ConfigObject):
400 """An generic class supporting tags.
404 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
407 def ValidateTag(cls, tag):
408 """Check if a tag is valid.
410 If the tag is invalid, an errors.TagError will be raised. The
411 function has no return value.
414 if not isinstance(tag, basestring):
415 raise errors.TagError("Invalid tag type (not a string)")
416 if len(tag) > constants.MAX_TAG_LEN:
417 raise errors.TagError("Tag too long (>%d characters)" %
418 constants.MAX_TAG_LEN)
420 raise errors.TagError("Tags cannot be empty")
421 if not cls.VALID_TAG_RE.match(tag):
422 raise errors.TagError("Tag contains invalid characters")
425 """Return the tags list.
428 tags = getattr(self, "tags", None)
430 tags = self.tags = set()
433 def AddTag(self, tag):
437 self.ValidateTag(tag)
438 tags = self.GetTags()
439 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
440 raise errors.TagError("Too many tags")
441 self.GetTags().add(tag)
443 def RemoveTag(self, tag):
447 self.ValidateTag(tag)
448 tags = self.GetTags()
452 raise errors.TagError("Tag not found")
455 """Taggable-object-specific conversion to standard python types.
457 This replaces the tags set with a list.
460 bo = super(TaggableObject, self).ToDict()
462 tags = bo.get("tags", None)
463 if isinstance(tags, set):
464 bo["tags"] = list(tags)
468 def FromDict(cls, val):
469 """Custom function for instances.
472 obj = super(TaggableObject, cls).FromDict(val)
473 if hasattr(obj, "tags") and isinstance(obj.tags, list):
474 obj.tags = set(obj.tags)
478 class MasterNetworkParameters(ConfigObject):
479 """Network configuration parameters for the master
481 @ivar name: master name
483 @ivar netmask: master netmask
484 @ivar netdev: master network device
485 @ivar ip_family: master IP family
497 class ConfigData(ConfigObject):
498 """Top-level config object."""
509 """Custom function for top-level config data.
511 This just replaces the list of instances, nodes and the cluster
512 with standard python types.
515 mydict = super(ConfigData, self).ToDict()
516 mydict["cluster"] = mydict["cluster"].ToDict()
517 for key in "nodes", "instances", "nodegroups":
518 mydict[key] = self._ContainerToDicts(mydict[key])
523 def FromDict(cls, val):
524 """Custom function for top-level config data
527 obj = super(ConfigData, cls).FromDict(val)
528 obj.cluster = Cluster.FromDict(obj.cluster)
529 obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
530 obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
531 obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
534 def HasAnyDiskOfType(self, dev_type):
535 """Check if in there is at disk of the given type in the configuration.
537 @type dev_type: L{constants.LDS_BLOCK}
538 @param dev_type: the type to look for
540 @return: boolean indicating if a disk of the given type was found or not
543 for instance in self.instances.values():
544 for disk in instance.disks:
545 if disk.IsBasedOnDiskType(dev_type):
549 def UpgradeConfig(self):
550 """Fill defaults for missing configuration values.
553 self.cluster.UpgradeConfig()
554 for node in self.nodes.values():
556 for instance in self.instances.values():
557 instance.UpgradeConfig()
558 if self.nodegroups is None:
560 for nodegroup in self.nodegroups.values():
561 nodegroup.UpgradeConfig()
562 if self.cluster.drbd_usermode_helper is None:
563 # To decide if we set an helper let's check if at least one instance has
564 # a DRBD disk. This does not cover all the possible scenarios but it
565 # gives a good approximation.
566 if self.HasAnyDiskOfType(constants.LD_DRBD8):
567 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
570 class NIC(ConfigObject):
571 """Config object representing a network card."""
572 __slots__ = ["mac", "ip", "nicparams"]
575 def CheckParameterSyntax(cls, nicparams):
576 """Check the given parameters for validity.
578 @type nicparams: dict
579 @param nicparams: dictionary with parameter names/value
580 @raise errors.ConfigurationError: when a parameter is not valid
583 if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
584 nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
585 err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
586 raise errors.ConfigurationError(err)
588 if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
589 not nicparams[constants.NIC_LINK]):
590 err = "Missing bridged nic link"
591 raise errors.ConfigurationError(err)
594 class Disk(ConfigObject):
595 """Config object representing a block device."""
596 __slots__ = ["dev_type", "logical_id", "physical_id",
597 "children", "iv_name", "size", "mode", "params"]
599 def CreateOnSecondary(self):
600 """Test if this device needs to be created on a secondary node."""
601 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
603 def AssembleOnSecondary(self):
604 """Test if this device needs to be assembled on a secondary node."""
605 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
607 def OpenOnSecondary(self):
608 """Test if this device needs to be opened on a secondary node."""
609 return self.dev_type in (constants.LD_LV,)
611 def StaticDevPath(self):
612 """Return the device path if this device type has a static one.
614 Some devices (LVM for example) live always at the same /dev/ path,
615 irrespective of their status. For such devices, we return this
616 path, for others we return None.
618 @warning: The path returned is not a normalized pathname; callers
619 should check that it is a valid path.
622 if self.dev_type == constants.LD_LV:
623 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
624 elif self.dev_type == constants.LD_BLOCKDEV:
625 return self.logical_id[1]
626 elif self.dev_type == constants.LD_RBD:
627 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
630 def ChildrenNeeded(self):
631 """Compute the needed number of children for activation.
633 This method will return either -1 (all children) or a positive
634 number denoting the minimum number of children needed for
635 activation (only mirrored devices will usually return >=0).
637 Currently, only DRBD8 supports diskless activation (therefore we
638 return 0), for all other we keep the previous semantics and return
642 if self.dev_type == constants.LD_DRBD8:
646 def IsBasedOnDiskType(self, dev_type):
647 """Check if the disk or its children are based on the given type.
649 @type dev_type: L{constants.LDS_BLOCK}
650 @param dev_type: the type to look for
652 @return: boolean indicating if a device of the given type was found or not
656 for child in self.children:
657 if child.IsBasedOnDiskType(dev_type):
659 return self.dev_type == dev_type
661 def GetNodes(self, node):
662 """This function returns the nodes this device lives on.
664 Given the node on which the parent of the device lives on (or, in
665 case of a top-level device, the primary node of the devices'
666 instance), this function will return a list of nodes on which this
667 devices needs to (or can) be assembled.
670 if self.dev_type in [constants.LD_LV, constants.LD_FILE,
671 constants.LD_BLOCKDEV, constants.LD_RBD]:
673 elif self.dev_type in constants.LDS_DRBD:
674 result = [self.logical_id[0], self.logical_id[1]]
675 if node not in result:
676 raise errors.ConfigurationError("DRBD device passed unknown node")
678 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
681 def ComputeNodeTree(self, parent_node):
682 """Compute the node/disk tree for this disk and its children.
684 This method, given the node on which the parent disk lives, will
685 return the list of all (node, disk) pairs which describe the disk
686 tree in the most compact way. For example, a drbd/lvm stack
687 will be returned as (primary_node, drbd) and (secondary_node, drbd)
688 which represents all the top-level devices on the nodes.
691 my_nodes = self.GetNodes(parent_node)
692 result = [(node, self) for node in my_nodes]
693 if not self.children:
696 for node in my_nodes:
697 for child in self.children:
698 child_result = child.ComputeNodeTree(node)
699 if len(child_result) == 1:
700 # child (and all its descendants) is simple, doesn't split
701 # over multiple hosts, so we don't need to describe it, our
702 # own entry for this node describes it completely
705 # check if child nodes differ from my nodes; note that
706 # subdisk can differ from the child itself, and be instead
707 # one of its descendants
708 for subnode, subdisk in child_result:
709 if subnode not in my_nodes:
710 result.append((subnode, subdisk))
711 # otherwise child is under our own node, so we ignore this
712 # entry (but probably the other results in the list will
716 def ComputeGrowth(self, amount):
717 """Compute the per-VG growth requirements.
719 This only works for VG-based disks.
721 @type amount: integer
722 @param amount: the desired increase in (user-visible) disk space
724 @return: a dictionary of volume-groups and the required size
727 if self.dev_type == constants.LD_LV:
728 return {self.logical_id[0]: amount}
729 elif self.dev_type == constants.LD_DRBD8:
731 return self.children[0].ComputeGrowth(amount)
735 # Other disk types do not require VG space
738 def RecordGrow(self, amount):
739 """Update the size of this disk after growth.
741 This method recurses over the disks's children and updates their
742 size correspondigly. The method needs to be kept in sync with the
743 actual algorithms from bdev.
746 if self.dev_type in (constants.LD_LV, constants.LD_FILE,
749 elif self.dev_type == constants.LD_DRBD8:
751 self.children[0].RecordGrow(amount)
754 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
755 " disk type %s" % self.dev_type)
757 def Update(self, size=None, mode=None):
758 """Apply changes to size and mode.
761 if self.dev_type == constants.LD_DRBD8:
763 self.children[0].Update(size=size, mode=mode)
765 assert not self.children
773 """Sets recursively the size to zero for the disk and its children.
777 for child in self.children:
781 def SetPhysicalID(self, target_node, nodes_ip):
782 """Convert the logical ID to the physical ID.
784 This is used only for drbd, which needs ip/port configuration.
786 The routine descends down and updates its children also, because
787 this helps when the only the top device is passed to the remote
791 - target_node: the node we wish to configure for
792 - nodes_ip: a mapping of node name to ip
794 The target_node must exist in in nodes_ip, and must be one of the
795 nodes in the logical ID for each of the DRBD devices encountered
800 for child in self.children:
801 child.SetPhysicalID(target_node, nodes_ip)
803 if self.logical_id is None and self.physical_id is not None:
805 if self.dev_type in constants.LDS_DRBD:
806 pnode, snode, port, pminor, sminor, secret = self.logical_id
807 if target_node not in (pnode, snode):
808 raise errors.ConfigurationError("DRBD device not knowing node %s" %
810 pnode_ip = nodes_ip.get(pnode, None)
811 snode_ip = nodes_ip.get(snode, None)
812 if pnode_ip is None or snode_ip is None:
813 raise errors.ConfigurationError("Can't find primary or secondary node"
814 " for %s" % str(self))
815 p_data = (pnode_ip, port)
816 s_data = (snode_ip, port)
817 if pnode == target_node:
818 self.physical_id = p_data + s_data + (pminor, secret)
819 else: # it must be secondary, we tested above
820 self.physical_id = s_data + p_data + (sminor, secret)
822 self.physical_id = self.logical_id
826 """Disk-specific conversion to standard python types.
828 This replaces the children lists of objects with lists of
829 standard python types.
832 bo = super(Disk, self).ToDict()
834 for attr in ("children",):
835 alist = bo.get(attr, None)
837 bo[attr] = self._ContainerToDicts(alist)
841 def FromDict(cls, val):
842 """Custom function for Disks
845 obj = super(Disk, cls).FromDict(val)
847 obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
848 if obj.logical_id and isinstance(obj.logical_id, list):
849 obj.logical_id = tuple(obj.logical_id)
850 if obj.physical_id and isinstance(obj.physical_id, list):
851 obj.physical_id = tuple(obj.physical_id)
852 if obj.dev_type in constants.LDS_DRBD:
853 # we need a tuple of length six here
854 if len(obj.logical_id) < 6:
855 obj.logical_id += (None,) * (6 - len(obj.logical_id))
859 """Custom str() formatter for disks.
862 if self.dev_type == constants.LD_LV:
863 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
864 elif self.dev_type in constants.LDS_DRBD:
865 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
867 if self.physical_id is None:
870 phy = ("configured as %s:%s %s:%s" %
871 (self.physical_id[0], self.physical_id[1],
872 self.physical_id[2], self.physical_id[3]))
874 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
875 (node_a, minor_a, node_b, minor_b, port, phy))
876 if self.children and self.children.count(None) == 0:
877 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
879 val += "no local storage"
881 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
882 (self.dev_type, self.logical_id, self.physical_id, self.children))
883 if self.iv_name is None:
884 val += ", not visible"
886 val += ", visible as /dev/%s" % self.iv_name
887 if isinstance(self.size, int):
888 val += ", size=%dm)>" % self.size
890 val += ", size='%s')>" % (self.size,)
894 """Checks that this disk is correctly configured.
898 if self.mode not in constants.DISK_ACCESS_SET:
899 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
902 def UpgradeConfig(self):
903 """Fill defaults for missing configuration values.
907 for child in self.children:
908 child.UpgradeConfig()
911 self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
913 self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
915 # add here config upgrade for this disk
918 class InstancePolicy(ConfigObject):
919 """Config object representing instance policy limits dictionary.
922 Note that this object is not actually used in the config, it's just
923 used as a placeholder for a few functions.
927 def CheckParameterSyntax(cls, ipolicy):
928 """ Check the instance policy for validity.
931 for param in constants.ISPECS_PARAMETERS:
932 InstancePolicy.CheckISpecSyntax(ipolicy, param)
933 if constants.IPOLICY_DTS in ipolicy:
934 InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
935 for key in constants.IPOLICY_PARAMETERS:
937 InstancePolicy.CheckParameter(key, ipolicy[key])
938 wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
940 raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
941 utils.CommaJoin(wrong_keys))
944 def CheckISpecSyntax(cls, ipolicy, name):
945 """Check the instance policy for validity on a given key.
947 We check if the instance policy makes sense for a given key, that is
948 if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
951 @param ipolicy: dictionary with min, max, std specs
953 @param name: what are the limits for
954 @raise errors.ConfigureError: when specs for given name are not valid
957 min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
958 std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
959 max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
960 err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
962 ipolicy[constants.ISPECS_MIN].get(name, "-"),
963 ipolicy[constants.ISPECS_MAX].get(name, "-"),
964 ipolicy[constants.ISPECS_STD].get(name, "-")))
965 if min_v > std_v or std_v > max_v:
966 raise errors.ConfigurationError(err)
969 def CheckDiskTemplates(cls, disk_templates):
970 """Checks the disk templates for validity.
973 wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
975 raise errors.ConfigurationError("Invalid disk template(s) %s" %
976 utils.CommaJoin(wrong))
979 def CheckParameter(cls, key, value):
980 """Checks a parameter.
982 Currently we expect all parameters to be float values.
987 except (TypeError, ValueError), err:
988 raise errors.ConfigurationError("Invalid value for key" " '%s':"
989 " '%s', error: %s" % (key, value, err))
992 class Instance(TaggableObject):
993 """Config object representing an instance."""
1008 ] + _TIMESTAMPS + _UUID
1010 def _ComputeSecondaryNodes(self):
1011 """Compute the list of secondary nodes.
1013 This is a simple wrapper over _ComputeAllNodes.
1016 all_nodes = set(self._ComputeAllNodes())
1017 all_nodes.discard(self.primary_node)
1018 return tuple(all_nodes)
1020 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1021 "List of secondary nodes")
1023 def _ComputeAllNodes(self):
1024 """Compute the list of all nodes.
1026 Since the data is already there (in the drbd disks), keeping it as
1027 a separate normal attribute is redundant and if not properly
1028 synchronised can cause problems. Thus it's better to compute it
1032 def _Helper(nodes, device):
1033 """Recursively computes nodes given a top device."""
1034 if device.dev_type in constants.LDS_DRBD:
1035 nodea, nodeb = device.logical_id[:2]
1039 for child in device.children:
1040 _Helper(nodes, child)
1043 all_nodes.add(self.primary_node)
1044 for device in self.disks:
1045 _Helper(all_nodes, device)
1046 return tuple(all_nodes)
1048 all_nodes = property(_ComputeAllNodes, None, None,
1049 "List of all nodes of the instance")
1051 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
1052 """Provide a mapping of nodes to LVs this instance owns.
1054 This function figures out what logical volumes should belong on
1055 which nodes, recursing through a device tree.
1057 @param lvmap: optional dictionary to receive the
1058 'node' : ['lv', ...] data.
1060 @return: None if lvmap arg is given, otherwise, a dictionary of
1061 the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1062 volumeN is of the form "vg_name/lv_name", compatible with
1067 node = self.primary_node
1075 if not node in lvmap:
1083 if dev.dev_type == constants.LD_LV:
1084 lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1086 elif dev.dev_type in constants.LDS_DRBD:
1088 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1089 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1092 self.MapLVsByNode(lvmap, dev.children, node)
1096 def FindDisk(self, idx):
1097 """Find a disk given having a specified index.
1099 This is just a wrapper that does validation of the index.
1102 @param idx: the disk index
1104 @return: the corresponding disk
1105 @raise errors.OpPrereqError: when the given index is not valid
1110 return self.disks[idx]
1111 except (TypeError, ValueError), err:
1112 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1115 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1116 " 0 to %d" % (idx, len(self.disks) - 1),
1120 """Instance-specific conversion to standard python types.
1122 This replaces the children lists of objects with lists of standard
1126 bo = super(Instance, self).ToDict()
1128 for attr in "nics", "disks":
1129 alist = bo.get(attr, None)
1131 nlist = self._ContainerToDicts(alist)
1138 def FromDict(cls, val):
1139 """Custom function for instances.
1142 if "admin_state" not in val:
1143 if val.get("admin_up", False):
1144 val["admin_state"] = constants.ADMINST_UP
1146 val["admin_state"] = constants.ADMINST_DOWN
1147 if "admin_up" in val:
1149 obj = super(Instance, cls).FromDict(val)
1150 obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1151 obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1154 def UpgradeConfig(self):
1155 """Fill defaults for missing configuration values.
1158 for nic in self.nics:
1160 for disk in self.disks:
1161 disk.UpgradeConfig()
1163 for key in constants.HVC_GLOBALS:
1165 del self.hvparams[key]
1168 if self.osparams is None:
1170 UpgradeBeParams(self.beparams)
1173 class OS(ConfigObject):
1174 """Config object representing an operating system.
1176 @type supported_parameters: list
1177 @ivar supported_parameters: a list of tuples, name and description,
1178 containing the supported parameters by this OS
1180 @type VARIANT_DELIM: string
1181 @cvar VARIANT_DELIM: the variant delimiter
1193 "supported_variants",
1194 "supported_parameters",
1200 def SplitNameVariant(cls, name):
1201 """Splits the name into the proper name and variant.
1203 @param name: the OS (unprocessed) name
1205 @return: a list of two elements; if the original name didn't
1206 contain a variant, it's returned as an empty string
1209 nv = name.split(cls.VARIANT_DELIM, 1)
1215 def GetName(cls, name):
1216 """Returns the proper name of the os (without the variant).
1218 @param name: the OS (unprocessed) name
1221 return cls.SplitNameVariant(name)[0]
1224 def GetVariant(cls, name):
1225 """Returns the variant the os (without the base name).
1227 @param name: the OS (unprocessed) name
1230 return cls.SplitNameVariant(name)[1]
1233 class NodeHvState(ConfigObject):
1234 """Hypvervisor state on a node.
1236 @ivar mem_total: Total amount of memory
1237 @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1239 @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1241 @ivar mem_inst: Memory used by instances living on node
1242 @ivar cpu_total: Total node CPU core count
1243 @ivar cpu_node: Number of CPU cores reserved for the node itself
1256 class NodeDiskState(ConfigObject):
1257 """Disk state on a node.
1267 class Node(TaggableObject):
1268 """Config object representing a node.
1270 @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1271 @ivar hv_state_static: Hypervisor state overriden by user
1272 @ivar disk_state: Disk state (e.g. free space)
1273 @ivar disk_state_static: Disk state overriden by user
1292 "disk_state_static",
1293 ] + _TIMESTAMPS + _UUID
1295 def UpgradeConfig(self):
1296 """Fill defaults for missing configuration values.
1299 # pylint: disable=E0203
1300 # because these are "defined" via slots, not manually
1301 if self.master_capable is None:
1302 self.master_capable = True
1304 if self.vm_capable is None:
1305 self.vm_capable = True
1307 if self.ndparams is None:
1310 if self.powered is None:
1314 """Custom function for serializing.
1317 data = super(Node, self).ToDict()
1319 hv_state = data.get("hv_state", None)
1320 if hv_state is not None:
1321 data["hv_state"] = self._ContainerToDicts(hv_state)
1323 disk_state = data.get("disk_state", None)
1324 if disk_state is not None:
1325 data["disk_state"] = \
1326 dict((key, self._ContainerToDicts(value))
1327 for (key, value) in disk_state.items())
1332 def FromDict(cls, val):
1333 """Custom function for deserializing.
1336 obj = super(Node, cls).FromDict(val)
1338 if obj.hv_state is not None:
1339 obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1341 if obj.disk_state is not None:
1343 dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1344 for (key, value) in obj.disk_state.items())
1349 class NodeGroup(TaggableObject):
1350 """Config object representing a node group."""
1359 "disk_state_static",
1361 ] + _TIMESTAMPS + _UUID
1364 """Custom function for nodegroup.
1366 This discards the members object, which gets recalculated and is only kept
1370 mydict = super(NodeGroup, self).ToDict()
1371 del mydict["members"]
1375 def FromDict(cls, val):
1376 """Custom function for nodegroup.
1378 The members slot is initialized to an empty list, upon deserialization.
1381 obj = super(NodeGroup, cls).FromDict(val)
1385 def UpgradeConfig(self):
1386 """Fill defaults for missing configuration values.
1389 if self.ndparams is None:
1392 if self.serial_no is None:
1395 if self.alloc_policy is None:
1396 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1398 # We only update mtime, and not ctime, since we would not be able
1399 # to provide a correct value for creation time.
1400 if self.mtime is None:
1401 self.mtime = time.time()
1403 self.diskparams = UpgradeDiskParams(self.diskparams)
1404 if self.ipolicy is None:
1405 self.ipolicy = MakeEmptyIPolicy()
1407 def FillND(self, node):
1408 """Return filled out ndparams for L{objects.Node}
1410 @type node: L{objects.Node}
1411 @param node: A Node object to fill
1412 @return a copy of the node's ndparams with defaults filled
1415 return self.SimpleFillND(node.ndparams)
1417 def SimpleFillND(self, ndparams):
1418 """Fill a given ndparams dict with defaults.
1420 @type ndparams: dict
1421 @param ndparams: the dict to fill
1423 @return: a copy of the passed in ndparams with missing keys filled
1424 from the node group defaults
1427 return FillDict(self.ndparams, ndparams)
1430 class Cluster(TaggableObject):
1431 """Config object representing the cluster."""
1435 "highest_used_port",
1438 "volume_group_name",
1440 "drbd_usermode_helper",
1442 "default_hypervisor",
1447 "use_external_mip_script",
1450 "shared_file_storage_dir",
1451 "enabled_hypervisors",
1460 "candidate_pool_size",
1463 "maintain_node_health",
1465 "default_iallocator",
1468 "primary_ip_family",
1469 "prealloc_wipe_disks",
1471 "disk_state_static",
1472 ] + _TIMESTAMPS + _UUID
1474 def UpgradeConfig(self):
1475 """Fill defaults for missing configuration values.
1478 # pylint: disable=E0203
1479 # because these are "defined" via slots, not manually
1480 if self.hvparams is None:
1481 self.hvparams = constants.HVC_DEFAULTS
1483 for hypervisor in self.hvparams:
1484 self.hvparams[hypervisor] = FillDict(
1485 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1487 if self.os_hvp is None:
1490 # osparams added before 2.2
1491 if self.osparams is None:
1494 self.ndparams = UpgradeNDParams(self.ndparams)
1496 self.beparams = UpgradeGroupedParams(self.beparams,
1497 constants.BEC_DEFAULTS)
1498 for beparams_group in self.beparams:
1499 UpgradeBeParams(self.beparams[beparams_group])
1501 migrate_default_bridge = not self.nicparams
1502 self.nicparams = UpgradeGroupedParams(self.nicparams,
1503 constants.NICC_DEFAULTS)
1504 if migrate_default_bridge:
1505 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1508 if self.modify_etc_hosts is None:
1509 self.modify_etc_hosts = True
1511 if self.modify_ssh_setup is None:
1512 self.modify_ssh_setup = True
1514 # default_bridge is no longer used in 2.1. The slot is left there to
1515 # support auto-upgrading. It can be removed once we decide to deprecate
1516 # upgrading straight from 2.0.
1517 if self.default_bridge is not None:
1518 self.default_bridge = None
1520 # default_hypervisor is just the first enabled one in 2.1. This slot and
1521 # code can be removed once upgrading straight from 2.0 is deprecated.
1522 if self.default_hypervisor is not None:
1523 self.enabled_hypervisors = ([self.default_hypervisor] +
1524 [hvname for hvname in self.enabled_hypervisors
1525 if hvname != self.default_hypervisor])
1526 self.default_hypervisor = None
1528 # maintain_node_health added after 2.1.1
1529 if self.maintain_node_health is None:
1530 self.maintain_node_health = False
1532 if self.uid_pool is None:
1535 if self.default_iallocator is None:
1536 self.default_iallocator = ""
1538 # reserved_lvs added before 2.2
1539 if self.reserved_lvs is None:
1540 self.reserved_lvs = []
1542 # hidden and blacklisted operating systems added before 2.2.1
1543 if self.hidden_os is None:
1546 if self.blacklisted_os is None:
1547 self.blacklisted_os = []
1549 # primary_ip_family added before 2.3
1550 if self.primary_ip_family is None:
1551 self.primary_ip_family = AF_INET
1553 if self.master_netmask is None:
1554 ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1555 self.master_netmask = ipcls.iplen
1557 if self.prealloc_wipe_disks is None:
1558 self.prealloc_wipe_disks = False
1560 # shared_file_storage_dir added before 2.5
1561 if self.shared_file_storage_dir is None:
1562 self.shared_file_storage_dir = ""
1564 if self.use_external_mip_script is None:
1565 self.use_external_mip_script = False
1567 self.diskparams = UpgradeDiskParams(self.diskparams)
1569 # instance policy added before 2.6
1570 if self.ipolicy is None:
1571 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1573 # we can either make sure to upgrade the ipolicy always, or only
1574 # do it in some corner cases (e.g. missing keys); note that this
1575 # will break any removal of keys from the ipolicy dict
1576 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1579 def primary_hypervisor(self):
1580 """The first hypervisor is the primary.
1582 Useful, for example, for L{Node}'s hv/disk state.
1585 return self.enabled_hypervisors[0]
1588 """Custom function for cluster.
1591 mydict = super(Cluster, self).ToDict()
1592 mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1596 def FromDict(cls, val):
1597 """Custom function for cluster.
1600 obj = super(Cluster, cls).FromDict(val)
1601 if not isinstance(obj.tcpudp_port_pool, set):
1602 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1605 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1606 """Get the default hypervisor parameters for the cluster.
1608 @param hypervisor: the hypervisor name
1609 @param os_name: if specified, we'll also update the defaults for this OS
1610 @param skip_keys: if passed, list of keys not to use
1611 @return: the defaults dict
1614 if skip_keys is None:
1617 fill_stack = [self.hvparams.get(hypervisor, {})]
1618 if os_name is not None:
1619 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1620 fill_stack.append(os_hvp)
1623 for o_dict in fill_stack:
1624 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1628 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1629 """Fill a given hvparams dict with cluster defaults.
1631 @type hv_name: string
1632 @param hv_name: the hypervisor to use
1633 @type os_name: string
1634 @param os_name: the OS to use for overriding the hypervisor defaults
1635 @type skip_globals: boolean
1636 @param skip_globals: if True, the global hypervisor parameters will
1639 @return: a copy of the given hvparams with missing keys filled from
1640 the cluster defaults
1644 skip_keys = constants.HVC_GLOBALS
1648 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1649 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1651 def FillHV(self, instance, skip_globals=False):
1652 """Fill an instance's hvparams dict with cluster defaults.
1654 @type instance: L{objects.Instance}
1655 @param instance: the instance parameter to fill
1656 @type skip_globals: boolean
1657 @param skip_globals: if True, the global hypervisor parameters will
1660 @return: a copy of the instance's hvparams with missing keys filled from
1661 the cluster defaults
1664 return self.SimpleFillHV(instance.hypervisor, instance.os,
1665 instance.hvparams, skip_globals)
1667 def SimpleFillBE(self, beparams):
1668 """Fill a given beparams dict with cluster defaults.
1670 @type beparams: dict
1671 @param beparams: the dict to fill
1673 @return: a copy of the passed in beparams with missing keys filled
1674 from the cluster defaults
1677 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1679 def FillBE(self, instance):
1680 """Fill an instance's beparams dict with cluster defaults.
1682 @type instance: L{objects.Instance}
1683 @param instance: the instance parameter to fill
1685 @return: a copy of the instance's beparams with missing keys filled from
1686 the cluster defaults
1689 return self.SimpleFillBE(instance.beparams)
1691 def SimpleFillNIC(self, nicparams):
1692 """Fill a given nicparams dict with cluster defaults.
1694 @type nicparams: dict
1695 @param nicparams: the dict to fill
1697 @return: a copy of the passed in nicparams with missing keys filled
1698 from the cluster defaults
1701 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1703 def SimpleFillOS(self, os_name, os_params):
1704 """Fill an instance's osparams dict with cluster defaults.
1706 @type os_name: string
1707 @param os_name: the OS name to use
1708 @type os_params: dict
1709 @param os_params: the dict to fill with default values
1711 @return: a copy of the instance's osparams with missing keys filled from
1712 the cluster defaults
1715 name_only = os_name.split("+", 1)[0]
1717 result = self.osparams.get(name_only, {})
1719 result = FillDict(result, self.osparams.get(os_name, {}))
1721 return FillDict(result, os_params)
1724 def SimpleFillHvState(hv_state):
1725 """Fill an hv_state sub dict with cluster defaults.
1728 return FillDict(constants.HVST_DEFAULTS, hv_state)
1731 def SimpleFillDiskState(disk_state):
1732 """Fill an disk_state sub dict with cluster defaults.
1735 return FillDict(constants.DS_DEFAULTS, disk_state)
1737 def FillND(self, node, nodegroup):
1738 """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1740 @type node: L{objects.Node}
1741 @param node: A Node object to fill
1742 @type nodegroup: L{objects.NodeGroup}
1743 @param nodegroup: A Node object to fill
1744 @return a copy of the node's ndparams with defaults filled
1747 return self.SimpleFillND(nodegroup.FillND(node))
1749 def SimpleFillND(self, ndparams):
1750 """Fill a given ndparams dict with defaults.
1752 @type ndparams: dict
1753 @param ndparams: the dict to fill
1755 @return: a copy of the passed in ndparams with missing keys filled
1756 from the cluster defaults
1759 return FillDict(self.ndparams, ndparams)
1761 def SimpleFillIPolicy(self, ipolicy):
1762 """ Fill instance policy dict with defaults.
1765 @param ipolicy: the dict to fill
1767 @return: a copy of passed ipolicy with missing keys filled from
1768 the cluster defaults
1771 return FillIPolicy(self.ipolicy, ipolicy)
1774 class BlockDevStatus(ConfigObject):
1775 """Config object representing the status of a block device."""
1787 class ImportExportStatus(ConfigObject):
1788 """Config object representing the status of an import or export."""
1794 "progress_throughput",
1802 class ImportExportOptions(ConfigObject):
1803 """Options for import/export daemon
1805 @ivar key_name: X509 key name (None for cluster certificate)
1806 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1807 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1808 @ivar magic: Used to ensure the connection goes to the right disk
1809 @ivar ipv6: Whether to use IPv6
1810 @ivar connect_timeout: Number of seconds for establishing connection
1823 class ConfdRequest(ConfigObject):
1824 """Object holding a confd request.
1826 @ivar protocol: confd protocol version
1827 @ivar type: confd query type
1828 @ivar query: query request
1829 @ivar rsalt: requested reply salt
1840 class ConfdReply(ConfigObject):
1841 """Object holding a confd reply.
1843 @ivar protocol: confd protocol version
1844 @ivar status: reply status code (ok, error)
1845 @ivar answer: confd query reply
1846 @ivar serial: configuration serial number
1857 class QueryFieldDefinition(ConfigObject):
1858 """Object holding a query field definition.
1860 @ivar name: Field name
1861 @ivar title: Human-readable title
1862 @ivar kind: Field type
1863 @ivar doc: Human-readable description
1874 class _QueryResponseBase(ConfigObject):
1880 """Custom function for serializing.
1883 mydict = super(_QueryResponseBase, self).ToDict()
1884 mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1888 def FromDict(cls, val):
1889 """Custom function for de-serializing.
1892 obj = super(_QueryResponseBase, cls).FromDict(val)
1893 obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1897 class QueryRequest(ConfigObject):
1898 """Object holding a query request.
1908 class QueryResponse(_QueryResponseBase):
1909 """Object holding the response to a query.
1911 @ivar fields: List of L{QueryFieldDefinition} objects
1912 @ivar data: Requested data
1920 class QueryFieldsRequest(ConfigObject):
1921 """Object holding a request for querying available fields.
1930 class QueryFieldsResponse(_QueryResponseBase):
1931 """Object holding the response to a query for fields.
1933 @ivar fields: List of L{QueryFieldDefinition} objects
1940 class MigrationStatus(ConfigObject):
1941 """Object holding the status of a migration.
1951 class InstanceConsole(ConfigObject):
1952 """Object describing how to access the console of an instance.
1967 """Validates contents of this object.
1970 assert self.kind in constants.CONS_ALL, "Unknown console type"
1971 assert self.instance, "Missing instance name"
1972 assert self.message or self.kind in [constants.CONS_SSH,
1973 constants.CONS_SPICE,
1975 assert self.host or self.kind == constants.CONS_MESSAGE
1976 assert self.port or self.kind in [constants.CONS_MESSAGE,
1978 assert self.user or self.kind in [constants.CONS_MESSAGE,
1979 constants.CONS_SPICE,
1981 assert self.command or self.kind in [constants.CONS_MESSAGE,
1982 constants.CONS_SPICE,
1984 assert self.display or self.kind in [constants.CONS_MESSAGE,
1985 constants.CONS_SPICE,
1990 class SerializableConfigParser(ConfigParser.SafeConfigParser):
1991 """Simple wrapper over ConfigParse that allows serialization.
1993 This class is basically ConfigParser.SafeConfigParser with two
1994 additional methods that allow it to serialize/unserialize to/from a
1999 """Dump this instance and return the string representation."""
2002 return buf.getvalue()
2005 def Loads(cls, data):
2006 """Load data from a string."""
2007 buf = StringIO(data)