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 explicitly 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 objectutils
48 from ganeti import utils
50 from socket import AF_INET
53 __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
54 "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network"]
56 _TIMESTAMPS = ["ctime", "mtime"]
60 def FillDict(defaults_dict, custom_dict, skip_keys=None):
61 """Basic function to apply settings on top a default dict.
63 @type defaults_dict: dict
64 @param defaults_dict: dictionary holding the default values
65 @type custom_dict: dict
66 @param custom_dict: dictionary holding customized value
68 @param skip_keys: which keys not to fill
70 @return: dict with the 'full' values
73 ret_dict = copy.deepcopy(defaults_dict)
74 ret_dict.update(custom_dict)
84 def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None):
85 """Fills an instance policy with defaults.
88 assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
90 for key in constants.IPOLICY_ISPECS:
91 ret_dict[key] = FillDict(default_ipolicy[key],
92 custom_ipolicy.get(key, {}),
95 for key in [constants.IPOLICY_DTS]:
96 ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
97 # other items which we know we can directly copy (immutables)
98 for key in constants.IPOLICY_PARAMETERS:
99 ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
104 def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
105 """Fills the disk parameter defaults.
107 @see: L{FillDict} for parameters and return value
110 assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
112 return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
113 skip_keys=skip_keys))
114 for dt in constants.DISK_TEMPLATES)
117 def UpgradeGroupedParams(target, defaults):
118 """Update all groups for the target parameter.
120 @type target: dict of dicts
121 @param target: {group: {parameter: value}}
123 @param defaults: default parameter values
127 target = {constants.PP_DEFAULT: defaults}
130 target[group] = FillDict(defaults, target[group])
134 def UpgradeBeParams(target):
135 """Update the be parameters dict to the new format.
138 @param target: "be" parameters dict
141 if constants.BE_MEMORY in target:
142 memory = target[constants.BE_MEMORY]
143 target[constants.BE_MAXMEM] = memory
144 target[constants.BE_MINMEM] = memory
145 del target[constants.BE_MEMORY]
148 def UpgradeDiskParams(diskparams):
149 """Upgrade the disk parameters.
151 @type diskparams: dict
152 @param diskparams: disk parameters to upgrade
154 @return: the upgraded disk parameters dict
160 result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
165 def UpgradeNDParams(ndparams):
166 """Upgrade ndparams structure.
169 @param ndparams: disk parameters to upgrade
171 @return: the upgraded node parameters dict
177 if (constants.ND_OOB_PROGRAM in ndparams and
178 ndparams[constants.ND_OOB_PROGRAM] is None):
179 # will be reset by the line below
180 del ndparams[constants.ND_OOB_PROGRAM]
181 return FillDict(constants.NDC_DEFAULTS, ndparams)
184 def MakeEmptyIPolicy():
185 """Create empty IPolicy dictionary.
189 (constants.ISPECS_MIN, {}),
190 (constants.ISPECS_MAX, {}),
191 (constants.ISPECS_STD, {}),
195 class ConfigObject(objectutils.ValidatedSlots):
196 """A generic config object.
198 It has the following properties:
200 - provides somewhat safe recursive unpickling and pickling for its classes
201 - unset attributes which are defined in slots are always returned
202 as None instead of raising an error
204 Classes derived from this must always declare __slots__ (we use many
205 config objects and the memory reduction is useful)
210 def __getattr__(self, name):
211 if name not in self.GetAllSlots():
212 raise AttributeError("Invalid object attribute %s.%s" %
213 (type(self).__name__, name))
216 def __setstate__(self, state):
217 slots = self.GetAllSlots()
220 setattr(self, name, state[name])
223 """Validates the slots.
228 """Convert to a dict holding only standard python types.
230 The generic routine just dumps all of this object's attributes in
231 a dict. It does not work if the class has children who are
232 ConfigObjects themselves (e.g. the nics list in an Instance), in
233 which case the object should subclass the function in order to
234 make sure all objects returned are only standard python types.
238 for name in self.GetAllSlots():
239 value = getattr(self, name, None)
240 if value is not None:
244 __getstate__ = ToDict
247 def FromDict(cls, val):
248 """Create an object from a dictionary.
250 This generic routine takes a dict, instantiates a new instance of
251 the given class, and sets attributes based on the dict content.
253 As for `ToDict`, this does not work if the class has children
254 who are ConfigObjects themselves (e.g. the nics list in an
255 Instance), in which case the object should subclass the function
256 and alter the objects.
259 if not isinstance(val, dict):
260 raise errors.ConfigurationError("Invalid object passed to FromDict:"
261 " expected dict, got %s" % type(val))
262 val_str = dict([(str(k), v) for k, v in val.iteritems()])
263 obj = cls(**val_str) # pylint: disable=W0142
267 """Makes a deep copy of the current object and its children.
270 dict_form = self.ToDict()
271 clone_obj = self.__class__.FromDict(dict_form)
275 """Implement __repr__ for ConfigObjects."""
276 return repr(self.ToDict())
278 def UpgradeConfig(self):
279 """Fill defaults for missing configuration values.
281 This method will be called at configuration load time, and its
282 implementation will be object dependent.
288 class TaggableObject(ConfigObject):
289 """An generic class supporting tags.
293 VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
296 def ValidateTag(cls, tag):
297 """Check if a tag is valid.
299 If the tag is invalid, an errors.TagError will be raised. The
300 function has no return value.
303 if not isinstance(tag, basestring):
304 raise errors.TagError("Invalid tag type (not a string)")
305 if len(tag) > constants.MAX_TAG_LEN:
306 raise errors.TagError("Tag too long (>%d characters)" %
307 constants.MAX_TAG_LEN)
309 raise errors.TagError("Tags cannot be empty")
310 if not cls.VALID_TAG_RE.match(tag):
311 raise errors.TagError("Tag contains invalid characters")
314 """Return the tags list.
317 tags = getattr(self, "tags", None)
319 tags = self.tags = set()
322 def AddTag(self, tag):
326 self.ValidateTag(tag)
327 tags = self.GetTags()
328 if len(tags) >= constants.MAX_TAGS_PER_OBJ:
329 raise errors.TagError("Too many tags")
330 self.GetTags().add(tag)
332 def RemoveTag(self, tag):
336 self.ValidateTag(tag)
337 tags = self.GetTags()
341 raise errors.TagError("Tag not found")
344 """Taggable-object-specific conversion to standard python types.
346 This replaces the tags set with a list.
349 bo = super(TaggableObject, self).ToDict()
351 tags = bo.get("tags", None)
352 if isinstance(tags, set):
353 bo["tags"] = list(tags)
357 def FromDict(cls, val):
358 """Custom function for instances.
361 obj = super(TaggableObject, cls).FromDict(val)
362 if hasattr(obj, "tags") and isinstance(obj.tags, list):
363 obj.tags = set(obj.tags)
367 class MasterNetworkParameters(ConfigObject):
368 """Network configuration parameters for the master
370 @ivar name: master name
372 @ivar netmask: master netmask
373 @ivar netdev: master network device
374 @ivar ip_family: master IP family
386 class ConfigData(ConfigObject):
387 """Top-level config object."""
399 """Custom function for top-level config data.
401 This just replaces the list of instances, nodes and the cluster
402 with standard python types.
405 mydict = super(ConfigData, self).ToDict()
406 mydict["cluster"] = mydict["cluster"].ToDict()
407 for key in "nodes", "instances", "nodegroups", "networks":
408 mydict[key] = objectutils.ContainerToDicts(mydict[key])
413 def FromDict(cls, val):
414 """Custom function for top-level config data
417 obj = super(ConfigData, cls).FromDict(val)
418 obj.cluster = Cluster.FromDict(obj.cluster)
419 obj.nodes = objectutils.ContainerFromDicts(obj.nodes, dict, Node)
421 objectutils.ContainerFromDicts(obj.instances, dict, Instance)
423 objectutils.ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
424 obj.networks = objectutils.ContainerFromDicts(obj.networks, dict, Network)
427 def HasAnyDiskOfType(self, dev_type):
428 """Check if in there is at disk of the given type in the configuration.
430 @type dev_type: L{constants.LDS_BLOCK}
431 @param dev_type: the type to look for
433 @return: boolean indicating if a disk of the given type was found or not
436 for instance in self.instances.values():
437 for disk in instance.disks:
438 if disk.IsBasedOnDiskType(dev_type):
442 def UpgradeConfig(self):
443 """Fill defaults for missing configuration values.
446 self.cluster.UpgradeConfig()
447 for node in self.nodes.values():
449 for instance in self.instances.values():
450 instance.UpgradeConfig()
451 if self.nodegroups is None:
453 for nodegroup in self.nodegroups.values():
454 nodegroup.UpgradeConfig()
455 if self.cluster.drbd_usermode_helper is None:
456 # To decide if we set an helper let's check if at least one instance has
457 # a DRBD disk. This does not cover all the possible scenarios but it
458 # gives a good approximation.
459 if self.HasAnyDiskOfType(constants.LD_DRBD8):
460 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
461 if self.networks is None:
465 class NIC(ConfigObject):
466 """Config object representing a network card."""
467 __slots__ = ["mac", "ip", "network", "nicparams", "netinfo"]
470 def CheckParameterSyntax(cls, nicparams):
471 """Check the given parameters for validity.
473 @type nicparams: dict
474 @param nicparams: dictionary with parameter names/value
475 @raise errors.ConfigurationError: when a parameter is not valid
478 mode = nicparams[constants.NIC_MODE]
479 if (mode not in constants.NIC_VALID_MODES and
480 mode != constants.VALUE_AUTO):
481 raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode)
483 if (mode == constants.NIC_MODE_BRIDGED and
484 not nicparams[constants.NIC_LINK]):
485 raise errors.ConfigurationError("Missing bridged NIC link")
488 class Disk(ConfigObject):
489 """Config object representing a block device."""
490 __slots__ = ["dev_type", "logical_id", "physical_id",
491 "children", "iv_name", "size", "mode", "params"]
493 def CreateOnSecondary(self):
494 """Test if this device needs to be created on a secondary node."""
495 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
497 def AssembleOnSecondary(self):
498 """Test if this device needs to be assembled on a secondary node."""
499 return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
501 def OpenOnSecondary(self):
502 """Test if this device needs to be opened on a secondary node."""
503 return self.dev_type in (constants.LD_LV,)
505 def StaticDevPath(self):
506 """Return the device path if this device type has a static one.
508 Some devices (LVM for example) live always at the same /dev/ path,
509 irrespective of their status. For such devices, we return this
510 path, for others we return None.
512 @warning: The path returned is not a normalized pathname; callers
513 should check that it is a valid path.
516 if self.dev_type == constants.LD_LV:
517 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
518 elif self.dev_type == constants.LD_BLOCKDEV:
519 return self.logical_id[1]
520 elif self.dev_type == constants.LD_RBD:
521 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
524 def ChildrenNeeded(self):
525 """Compute the needed number of children for activation.
527 This method will return either -1 (all children) or a positive
528 number denoting the minimum number of children needed for
529 activation (only mirrored devices will usually return >=0).
531 Currently, only DRBD8 supports diskless activation (therefore we
532 return 0), for all other we keep the previous semantics and return
536 if self.dev_type == constants.LD_DRBD8:
540 def IsBasedOnDiskType(self, dev_type):
541 """Check if the disk or its children are based on the given type.
543 @type dev_type: L{constants.LDS_BLOCK}
544 @param dev_type: the type to look for
546 @return: boolean indicating if a device of the given type was found or not
550 for child in self.children:
551 if child.IsBasedOnDiskType(dev_type):
553 return self.dev_type == dev_type
555 def GetNodes(self, node):
556 """This function returns the nodes this device lives on.
558 Given the node on which the parent of the device lives on (or, in
559 case of a top-level device, the primary node of the devices'
560 instance), this function will return a list of nodes on which this
561 devices needs to (or can) be assembled.
564 if self.dev_type in [constants.LD_LV, constants.LD_FILE,
565 constants.LD_BLOCKDEV, constants.LD_RBD,
568 elif self.dev_type in constants.LDS_DRBD:
569 result = [self.logical_id[0], self.logical_id[1]]
570 if node not in result:
571 raise errors.ConfigurationError("DRBD device passed unknown node")
573 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
576 def ComputeNodeTree(self, parent_node):
577 """Compute the node/disk tree for this disk and its children.
579 This method, given the node on which the parent disk lives, will
580 return the list of all (node, disk) pairs which describe the disk
581 tree in the most compact way. For example, a drbd/lvm stack
582 will be returned as (primary_node, drbd) and (secondary_node, drbd)
583 which represents all the top-level devices on the nodes.
586 my_nodes = self.GetNodes(parent_node)
587 result = [(node, self) for node in my_nodes]
588 if not self.children:
591 for node in my_nodes:
592 for child in self.children:
593 child_result = child.ComputeNodeTree(node)
594 if len(child_result) == 1:
595 # child (and all its descendants) is simple, doesn't split
596 # over multiple hosts, so we don't need to describe it, our
597 # own entry for this node describes it completely
600 # check if child nodes differ from my nodes; note that
601 # subdisk can differ from the child itself, and be instead
602 # one of its descendants
603 for subnode, subdisk in child_result:
604 if subnode not in my_nodes:
605 result.append((subnode, subdisk))
606 # otherwise child is under our own node, so we ignore this
607 # entry (but probably the other results in the list will
611 def ComputeGrowth(self, amount):
612 """Compute the per-VG growth requirements.
614 This only works for VG-based disks.
616 @type amount: integer
617 @param amount: the desired increase in (user-visible) disk space
619 @return: a dictionary of volume-groups and the required size
622 if self.dev_type == constants.LD_LV:
623 return {self.logical_id[0]: amount}
624 elif self.dev_type == constants.LD_DRBD8:
626 return self.children[0].ComputeGrowth(amount)
630 # Other disk types do not require VG space
633 def RecordGrow(self, amount):
634 """Update the size of this disk after growth.
636 This method recurses over the disks's children and updates their
637 size correspondigly. The method needs to be kept in sync with the
638 actual algorithms from bdev.
641 if self.dev_type in (constants.LD_LV, constants.LD_FILE,
642 constants.LD_RBD, constants.LD_EXT):
644 elif self.dev_type == constants.LD_DRBD8:
646 self.children[0].RecordGrow(amount)
649 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
650 " disk type %s" % self.dev_type)
652 def Update(self, size=None, mode=None):
653 """Apply changes to size and mode.
656 if self.dev_type == constants.LD_DRBD8:
658 self.children[0].Update(size=size, mode=mode)
660 assert not self.children
668 """Sets recursively the size to zero for the disk and its children.
672 for child in self.children:
676 def SetPhysicalID(self, target_node, nodes_ip):
677 """Convert the logical ID to the physical ID.
679 This is used only for drbd, which needs ip/port configuration.
681 The routine descends down and updates its children also, because
682 this helps when the only the top device is passed to the remote
686 - target_node: the node we wish to configure for
687 - nodes_ip: a mapping of node name to ip
689 The target_node must exist in in nodes_ip, and must be one of the
690 nodes in the logical ID for each of the DRBD devices encountered
695 for child in self.children:
696 child.SetPhysicalID(target_node, nodes_ip)
698 if self.logical_id is None and self.physical_id is not None:
700 if self.dev_type in constants.LDS_DRBD:
701 pnode, snode, port, pminor, sminor, secret = self.logical_id
702 if target_node not in (pnode, snode):
703 raise errors.ConfigurationError("DRBD device not knowing node %s" %
705 pnode_ip = nodes_ip.get(pnode, None)
706 snode_ip = nodes_ip.get(snode, None)
707 if pnode_ip is None or snode_ip is None:
708 raise errors.ConfigurationError("Can't find primary or secondary node"
709 " for %s" % str(self))
710 p_data = (pnode_ip, port)
711 s_data = (snode_ip, port)
712 if pnode == target_node:
713 self.physical_id = p_data + s_data + (pminor, secret)
714 else: # it must be secondary, we tested above
715 self.physical_id = s_data + p_data + (sminor, secret)
717 self.physical_id = self.logical_id
721 """Disk-specific conversion to standard python types.
723 This replaces the children lists of objects with lists of
724 standard python types.
727 bo = super(Disk, self).ToDict()
729 for attr in ("children",):
730 alist = bo.get(attr, None)
732 bo[attr] = objectutils.ContainerToDicts(alist)
736 def FromDict(cls, val):
737 """Custom function for Disks
740 obj = super(Disk, cls).FromDict(val)
742 obj.children = objectutils.ContainerFromDicts(obj.children, list, Disk)
743 if obj.logical_id and isinstance(obj.logical_id, list):
744 obj.logical_id = tuple(obj.logical_id)
745 if obj.physical_id and isinstance(obj.physical_id, list):
746 obj.physical_id = tuple(obj.physical_id)
747 if obj.dev_type in constants.LDS_DRBD:
748 # we need a tuple of length six here
749 if len(obj.logical_id) < 6:
750 obj.logical_id += (None,) * (6 - len(obj.logical_id))
754 """Custom str() formatter for disks.
757 if self.dev_type == constants.LD_LV:
758 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
759 elif self.dev_type in constants.LDS_DRBD:
760 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
762 if self.physical_id is None:
765 phy = ("configured as %s:%s %s:%s" %
766 (self.physical_id[0], self.physical_id[1],
767 self.physical_id[2], self.physical_id[3]))
769 val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
770 (node_a, minor_a, node_b, minor_b, port, phy))
771 if self.children and self.children.count(None) == 0:
772 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
774 val += "no local storage"
776 val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
777 (self.dev_type, self.logical_id, self.physical_id, self.children))
778 if self.iv_name is None:
779 val += ", not visible"
781 val += ", visible as /dev/%s" % self.iv_name
782 if isinstance(self.size, int):
783 val += ", size=%dm)>" % self.size
785 val += ", size='%s')>" % (self.size,)
789 """Checks that this disk is correctly configured.
793 if self.mode not in constants.DISK_ACCESS_SET:
794 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
797 def UpgradeConfig(self):
798 """Fill defaults for missing configuration values.
802 for child in self.children:
803 child.UpgradeConfig()
805 # FIXME: Make this configurable in Ganeti 2.7
807 # add here config upgrade for this disk
810 def ComputeLDParams(disk_template, disk_params):
811 """Computes Logical Disk parameters from Disk Template parameters.
813 @type disk_template: string
814 @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
815 @type disk_params: dict
816 @param disk_params: disk template parameters;
817 dict(template_name -> parameters
819 @return: a list of dicts, one for each node of the disk hierarchy. Each dict
820 contains the LD parameters of the node. The tree is flattened in-order.
823 if disk_template not in constants.DISK_TEMPLATES:
824 raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
826 assert disk_template in disk_params
829 dt_params = disk_params[disk_template]
830 if disk_template == constants.DT_DRBD8:
831 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8], {
832 constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
833 constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
834 constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
835 constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
836 constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
837 constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
838 constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
839 constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
840 constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
841 constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
842 constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
843 constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
847 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
848 constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
852 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
853 constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
856 elif disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
857 result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
859 elif disk_template == constants.DT_PLAIN:
860 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], {
861 constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
864 elif disk_template == constants.DT_BLOCK:
865 result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
867 elif disk_template == constants.DT_RBD:
868 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], {
869 constants.LDP_POOL: dt_params[constants.RBD_POOL],
872 elif disk_template == constants.DT_EXT:
873 result.append(constants.DISK_LD_DEFAULTS[constants.LD_EXT])
878 class InstancePolicy(ConfigObject):
879 """Config object representing instance policy limits dictionary.
882 Note that this object is not actually used in the config, it's just
883 used as a placeholder for a few functions.
887 def CheckParameterSyntax(cls, ipolicy, check_std):
888 """ Check the instance policy for validity.
891 for param in constants.ISPECS_PARAMETERS:
892 InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std)
893 if constants.IPOLICY_DTS in ipolicy:
894 InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
895 for key in constants.IPOLICY_PARAMETERS:
897 InstancePolicy.CheckParameter(key, ipolicy[key])
898 wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
900 raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
901 utils.CommaJoin(wrong_keys))
904 def CheckISpecSyntax(cls, ipolicy, name, check_std):
905 """Check the instance policy for validity on a given key.
907 We check if the instance policy makes sense for a given key, that is
908 if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
911 @param ipolicy: dictionary with min, max, std specs
913 @param name: what are the limits for
914 @type check_std: bool
915 @param check_std: Whether to check std value or just assume compliance
916 @raise errors.ConfigureError: when specs for given name are not valid
919 min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
922 std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
928 max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
929 err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
931 ipolicy[constants.ISPECS_MIN].get(name, "-"),
932 ipolicy[constants.ISPECS_MAX].get(name, "-"),
934 if min_v > std_v or std_v > max_v:
935 raise errors.ConfigurationError(err)
938 def CheckDiskTemplates(cls, disk_templates):
939 """Checks the disk templates for validity.
942 wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
944 raise errors.ConfigurationError("Invalid disk template(s) %s" %
945 utils.CommaJoin(wrong))
948 def CheckParameter(cls, key, value):
949 """Checks a parameter.
951 Currently we expect all parameters to be float values.
956 except (TypeError, ValueError), err:
957 raise errors.ConfigurationError("Invalid value for key" " '%s':"
958 " '%s', error: %s" % (key, value, err))
961 class Instance(TaggableObject):
962 """Config object representing an instance."""
977 ] + _TIMESTAMPS + _UUID
979 def _ComputeSecondaryNodes(self):
980 """Compute the list of secondary nodes.
982 This is a simple wrapper over _ComputeAllNodes.
985 all_nodes = set(self._ComputeAllNodes())
986 all_nodes.discard(self.primary_node)
987 return tuple(all_nodes)
989 secondary_nodes = property(_ComputeSecondaryNodes, None, None,
990 "List of names of secondary nodes")
992 def _ComputeAllNodes(self):
993 """Compute the list of all nodes.
995 Since the data is already there (in the drbd disks), keeping it as
996 a separate normal attribute is redundant and if not properly
997 synchronised can cause problems. Thus it's better to compute it
1001 def _Helper(nodes, device):
1002 """Recursively computes nodes given a top device."""
1003 if device.dev_type in constants.LDS_DRBD:
1004 nodea, nodeb = device.logical_id[:2]
1008 for child in device.children:
1009 _Helper(nodes, child)
1012 all_nodes.add(self.primary_node)
1013 for device in self.disks:
1014 _Helper(all_nodes, device)
1015 return tuple(all_nodes)
1017 all_nodes = property(_ComputeAllNodes, None, None,
1018 "List of names of all the nodes of the instance")
1020 def MapLVsByNode(self, lvmap=None, devs=None, node=None):
1021 """Provide a mapping of nodes to LVs this instance owns.
1023 This function figures out what logical volumes should belong on
1024 which nodes, recursing through a device tree.
1026 @param lvmap: optional dictionary to receive the
1027 'node' : ['lv', ...] data.
1029 @return: None if lvmap arg is given, otherwise, a dictionary of
1030 the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1031 volumeN is of the form "vg_name/lv_name", compatible with
1036 node = self.primary_node
1044 if not node in lvmap:
1052 if dev.dev_type == constants.LD_LV:
1053 lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1055 elif dev.dev_type in constants.LDS_DRBD:
1057 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1058 self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1061 self.MapLVsByNode(lvmap, dev.children, node)
1065 def FindDisk(self, idx):
1066 """Find a disk given having a specified index.
1068 This is just a wrapper that does validation of the index.
1071 @param idx: the disk index
1073 @return: the corresponding disk
1074 @raise errors.OpPrereqError: when the given index is not valid
1079 return self.disks[idx]
1080 except (TypeError, ValueError), err:
1081 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1084 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1085 " 0 to %d" % (idx, len(self.disks) - 1),
1089 """Instance-specific conversion to standard python types.
1091 This replaces the children lists of objects with lists of standard
1095 bo = super(Instance, self).ToDict()
1097 for attr in "nics", "disks":
1098 alist = bo.get(attr, None)
1100 nlist = objectutils.ContainerToDicts(alist)
1107 def FromDict(cls, val):
1108 """Custom function for instances.
1111 if "admin_state" not in val:
1112 if val.get("admin_up", False):
1113 val["admin_state"] = constants.ADMINST_UP
1115 val["admin_state"] = constants.ADMINST_DOWN
1116 if "admin_up" in val:
1118 obj = super(Instance, cls).FromDict(val)
1119 obj.nics = objectutils.ContainerFromDicts(obj.nics, list, NIC)
1120 obj.disks = objectutils.ContainerFromDicts(obj.disks, list, Disk)
1123 def UpgradeConfig(self):
1124 """Fill defaults for missing configuration values.
1127 for nic in self.nics:
1129 for disk in self.disks:
1130 disk.UpgradeConfig()
1132 for key in constants.HVC_GLOBALS:
1134 del self.hvparams[key]
1137 if self.osparams is None:
1139 UpgradeBeParams(self.beparams)
1142 class OS(ConfigObject):
1143 """Config object representing an operating system.
1145 @type supported_parameters: list
1146 @ivar supported_parameters: a list of tuples, name and description,
1147 containing the supported parameters by this OS
1149 @type VARIANT_DELIM: string
1150 @cvar VARIANT_DELIM: the variant delimiter
1162 "supported_variants",
1163 "supported_parameters",
1169 def SplitNameVariant(cls, name):
1170 """Splits the name into the proper name and variant.
1172 @param name: the OS (unprocessed) name
1174 @return: a list of two elements; if the original name didn't
1175 contain a variant, it's returned as an empty string
1178 nv = name.split(cls.VARIANT_DELIM, 1)
1184 def GetName(cls, name):
1185 """Returns the proper name of the os (without the variant).
1187 @param name: the OS (unprocessed) name
1190 return cls.SplitNameVariant(name)[0]
1193 def GetVariant(cls, name):
1194 """Returns the variant the os (without the base name).
1196 @param name: the OS (unprocessed) name
1199 return cls.SplitNameVariant(name)[1]
1202 class ExtStorage(ConfigObject):
1203 """Config object representing an External Storage Provider.
1216 "supported_parameters",
1220 class NodeHvState(ConfigObject):
1221 """Hypvervisor state on a node.
1223 @ivar mem_total: Total amount of memory
1224 @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1226 @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1228 @ivar mem_inst: Memory used by instances living on node
1229 @ivar cpu_total: Total node CPU core count
1230 @ivar cpu_node: Number of CPU cores reserved for the node itself
1243 class NodeDiskState(ConfigObject):
1244 """Disk state on a node.
1254 class Node(TaggableObject):
1255 """Config object representing a node.
1257 @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1258 @ivar hv_state_static: Hypervisor state overriden by user
1259 @ivar disk_state: Disk state (e.g. free space)
1260 @ivar disk_state_static: Disk state overriden by user
1279 "disk_state_static",
1280 ] + _TIMESTAMPS + _UUID
1282 def UpgradeConfig(self):
1283 """Fill defaults for missing configuration values.
1286 # pylint: disable=E0203
1287 # because these are "defined" via slots, not manually
1288 if self.master_capable is None:
1289 self.master_capable = True
1291 if self.vm_capable is None:
1292 self.vm_capable = True
1294 if self.ndparams is None:
1297 if self.powered is None:
1301 """Custom function for serializing.
1304 data = super(Node, self).ToDict()
1306 hv_state = data.get("hv_state", None)
1307 if hv_state is not None:
1308 data["hv_state"] = objectutils.ContainerToDicts(hv_state)
1310 disk_state = data.get("disk_state", None)
1311 if disk_state is not None:
1312 data["disk_state"] = \
1313 dict((key, objectutils.ContainerToDicts(value))
1314 for (key, value) in disk_state.items())
1319 def FromDict(cls, val):
1320 """Custom function for deserializing.
1323 obj = super(Node, cls).FromDict(val)
1325 if obj.hv_state is not None:
1327 objectutils.ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1329 if obj.disk_state is not None:
1331 dict((key, objectutils.ContainerFromDicts(value, dict, NodeDiskState))
1332 for (key, value) in obj.disk_state.items())
1337 class NodeGroup(TaggableObject):
1338 """Config object representing a node group."""
1347 "disk_state_static",
1350 ] + _TIMESTAMPS + _UUID
1353 """Custom function for nodegroup.
1355 This discards the members object, which gets recalculated and is only kept
1359 mydict = super(NodeGroup, self).ToDict()
1360 del mydict["members"]
1364 def FromDict(cls, val):
1365 """Custom function for nodegroup.
1367 The members slot is initialized to an empty list, upon deserialization.
1370 obj = super(NodeGroup, cls).FromDict(val)
1374 def UpgradeConfig(self):
1375 """Fill defaults for missing configuration values.
1378 if self.ndparams is None:
1381 if self.serial_no is None:
1384 if self.alloc_policy is None:
1385 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1387 # We only update mtime, and not ctime, since we would not be able
1388 # to provide a correct value for creation time.
1389 if self.mtime is None:
1390 self.mtime = time.time()
1392 if self.diskparams is None:
1393 self.diskparams = {}
1394 if self.ipolicy is None:
1395 self.ipolicy = MakeEmptyIPolicy()
1397 if self.networks is None:
1400 def FillND(self, node):
1401 """Return filled out ndparams for L{objects.Node}
1403 @type node: L{objects.Node}
1404 @param node: A Node object to fill
1405 @return a copy of the node's ndparams with defaults filled
1408 return self.SimpleFillND(node.ndparams)
1410 def SimpleFillND(self, ndparams):
1411 """Fill a given ndparams dict with defaults.
1413 @type ndparams: dict
1414 @param ndparams: the dict to fill
1416 @return: a copy of the passed in ndparams with missing keys filled
1417 from the node group defaults
1420 return FillDict(self.ndparams, ndparams)
1423 class Cluster(TaggableObject):
1424 """Config object representing the cluster."""
1428 "highest_used_port",
1431 "volume_group_name",
1433 "drbd_usermode_helper",
1435 "default_hypervisor",
1440 "use_external_mip_script",
1443 "shared_file_storage_dir",
1444 "enabled_hypervisors",
1453 "candidate_pool_size",
1456 "maintain_node_health",
1458 "default_iallocator",
1461 "primary_ip_family",
1462 "prealloc_wipe_disks",
1464 "disk_state_static",
1465 ] + _TIMESTAMPS + _UUID
1467 def UpgradeConfig(self):
1468 """Fill defaults for missing configuration values.
1471 # pylint: disable=E0203
1472 # because these are "defined" via slots, not manually
1473 if self.hvparams is None:
1474 self.hvparams = constants.HVC_DEFAULTS
1476 for hypervisor in self.hvparams:
1477 self.hvparams[hypervisor] = FillDict(
1478 constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1480 if self.os_hvp is None:
1483 # osparams added before 2.2
1484 if self.osparams is None:
1487 self.ndparams = UpgradeNDParams(self.ndparams)
1489 self.beparams = UpgradeGroupedParams(self.beparams,
1490 constants.BEC_DEFAULTS)
1491 for beparams_group in self.beparams:
1492 UpgradeBeParams(self.beparams[beparams_group])
1494 migrate_default_bridge = not self.nicparams
1495 self.nicparams = UpgradeGroupedParams(self.nicparams,
1496 constants.NICC_DEFAULTS)
1497 if migrate_default_bridge:
1498 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1501 if self.modify_etc_hosts is None:
1502 self.modify_etc_hosts = True
1504 if self.modify_ssh_setup is None:
1505 self.modify_ssh_setup = True
1507 # default_bridge is no longer used in 2.1. The slot is left there to
1508 # support auto-upgrading. It can be removed once we decide to deprecate
1509 # upgrading straight from 2.0.
1510 if self.default_bridge is not None:
1511 self.default_bridge = None
1513 # default_hypervisor is just the first enabled one in 2.1. This slot and
1514 # code can be removed once upgrading straight from 2.0 is deprecated.
1515 if self.default_hypervisor is not None:
1516 self.enabled_hypervisors = ([self.default_hypervisor] +
1517 [hvname for hvname in self.enabled_hypervisors
1518 if hvname != self.default_hypervisor])
1519 self.default_hypervisor = None
1521 # maintain_node_health added after 2.1.1
1522 if self.maintain_node_health is None:
1523 self.maintain_node_health = False
1525 if self.uid_pool is None:
1528 if self.default_iallocator is None:
1529 self.default_iallocator = ""
1531 # reserved_lvs added before 2.2
1532 if self.reserved_lvs is None:
1533 self.reserved_lvs = []
1535 # hidden and blacklisted operating systems added before 2.2.1
1536 if self.hidden_os is None:
1539 if self.blacklisted_os is None:
1540 self.blacklisted_os = []
1542 # primary_ip_family added before 2.3
1543 if self.primary_ip_family is None:
1544 self.primary_ip_family = AF_INET
1546 if self.master_netmask is None:
1547 ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1548 self.master_netmask = ipcls.iplen
1550 if self.prealloc_wipe_disks is None:
1551 self.prealloc_wipe_disks = False
1553 # shared_file_storage_dir added before 2.5
1554 if self.shared_file_storage_dir is None:
1555 self.shared_file_storage_dir = ""
1557 if self.use_external_mip_script is None:
1558 self.use_external_mip_script = False
1561 self.diskparams = UpgradeDiskParams(self.diskparams)
1563 self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1565 # instance policy added before 2.6
1566 if self.ipolicy is None:
1567 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1569 # we can either make sure to upgrade the ipolicy always, or only
1570 # do it in some corner cases (e.g. missing keys); note that this
1571 # will break any removal of keys from the ipolicy dict
1572 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1575 def primary_hypervisor(self):
1576 """The first hypervisor is the primary.
1578 Useful, for example, for L{Node}'s hv/disk state.
1581 return self.enabled_hypervisors[0]
1584 """Custom function for cluster.
1587 mydict = super(Cluster, self).ToDict()
1589 if self.tcpudp_port_pool is None:
1590 tcpudp_port_pool = []
1592 tcpudp_port_pool = list(self.tcpudp_port_pool)
1594 mydict["tcpudp_port_pool"] = tcpudp_port_pool
1599 def FromDict(cls, val):
1600 """Custom function for cluster.
1603 obj = super(Cluster, cls).FromDict(val)
1605 if obj.tcpudp_port_pool is None:
1606 obj.tcpudp_port_pool = set()
1607 elif not isinstance(obj.tcpudp_port_pool, set):
1608 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1612 def SimpleFillDP(self, diskparams):
1613 """Fill a given diskparams dict with cluster defaults.
1615 @param diskparams: The diskparams
1616 @return: The defaults dict
1619 return FillDiskParams(self.diskparams, diskparams)
1621 def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1622 """Get the default hypervisor parameters for the cluster.
1624 @param hypervisor: the hypervisor name
1625 @param os_name: if specified, we'll also update the defaults for this OS
1626 @param skip_keys: if passed, list of keys not to use
1627 @return: the defaults dict
1630 if skip_keys is None:
1633 fill_stack = [self.hvparams.get(hypervisor, {})]
1634 if os_name is not None:
1635 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1636 fill_stack.append(os_hvp)
1639 for o_dict in fill_stack:
1640 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1644 def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1645 """Fill a given hvparams dict with cluster defaults.
1647 @type hv_name: string
1648 @param hv_name: the hypervisor to use
1649 @type os_name: string
1650 @param os_name: the OS to use for overriding the hypervisor defaults
1651 @type skip_globals: boolean
1652 @param skip_globals: if True, the global hypervisor parameters will
1655 @return: a copy of the given hvparams with missing keys filled from
1656 the cluster defaults
1660 skip_keys = constants.HVC_GLOBALS
1664 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1665 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1667 def FillHV(self, instance, skip_globals=False):
1668 """Fill an instance's hvparams dict with cluster defaults.
1670 @type instance: L{objects.Instance}
1671 @param instance: the instance parameter to fill
1672 @type skip_globals: boolean
1673 @param skip_globals: if True, the global hypervisor parameters will
1676 @return: a copy of the instance's hvparams with missing keys filled from
1677 the cluster defaults
1680 return self.SimpleFillHV(instance.hypervisor, instance.os,
1681 instance.hvparams, skip_globals)
1683 def SimpleFillBE(self, beparams):
1684 """Fill a given beparams dict with cluster defaults.
1686 @type beparams: dict
1687 @param beparams: the dict to fill
1689 @return: a copy of the passed in beparams with missing keys filled
1690 from the cluster defaults
1693 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1695 def FillBE(self, instance):
1696 """Fill an instance's beparams dict with cluster defaults.
1698 @type instance: L{objects.Instance}
1699 @param instance: the instance parameter to fill
1701 @return: a copy of the instance's beparams with missing keys filled from
1702 the cluster defaults
1705 return self.SimpleFillBE(instance.beparams)
1707 def SimpleFillNIC(self, nicparams):
1708 """Fill a given nicparams dict with cluster defaults.
1710 @type nicparams: dict
1711 @param nicparams: the dict to fill
1713 @return: a copy of the passed in nicparams with missing keys filled
1714 from the cluster defaults
1717 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1719 def SimpleFillOS(self, os_name, os_params):
1720 """Fill an instance's osparams dict with cluster defaults.
1722 @type os_name: string
1723 @param os_name: the OS name to use
1724 @type os_params: dict
1725 @param os_params: the dict to fill with default values
1727 @return: a copy of the instance's osparams with missing keys filled from
1728 the cluster defaults
1731 name_only = os_name.split("+", 1)[0]
1733 result = self.osparams.get(name_only, {})
1735 result = FillDict(result, self.osparams.get(os_name, {}))
1737 return FillDict(result, os_params)
1740 def SimpleFillHvState(hv_state):
1741 """Fill an hv_state sub dict with cluster defaults.
1744 return FillDict(constants.HVST_DEFAULTS, hv_state)
1747 def SimpleFillDiskState(disk_state):
1748 """Fill an disk_state sub dict with cluster defaults.
1751 return FillDict(constants.DS_DEFAULTS, disk_state)
1753 def FillND(self, node, nodegroup):
1754 """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1756 @type node: L{objects.Node}
1757 @param node: A Node object to fill
1758 @type nodegroup: L{objects.NodeGroup}
1759 @param nodegroup: A Node object to fill
1760 @return a copy of the node's ndparams with defaults filled
1763 return self.SimpleFillND(nodegroup.FillND(node))
1765 def SimpleFillND(self, ndparams):
1766 """Fill a given ndparams dict with defaults.
1768 @type ndparams: dict
1769 @param ndparams: the dict to fill
1771 @return: a copy of the passed in ndparams with missing keys filled
1772 from the cluster defaults
1775 return FillDict(self.ndparams, ndparams)
1777 def SimpleFillIPolicy(self, ipolicy):
1778 """ Fill instance policy dict with defaults.
1781 @param ipolicy: the dict to fill
1783 @return: a copy of passed ipolicy with missing keys filled from
1784 the cluster defaults
1787 return FillIPolicy(self.ipolicy, ipolicy)
1790 class BlockDevStatus(ConfigObject):
1791 """Config object representing the status of a block device."""
1803 class ImportExportStatus(ConfigObject):
1804 """Config object representing the status of an import or export."""
1810 "progress_throughput",
1818 class ImportExportOptions(ConfigObject):
1819 """Options for import/export daemon
1821 @ivar key_name: X509 key name (None for cluster certificate)
1822 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1823 @ivar compress: Compression method (one of L{constants.IEC_ALL})
1824 @ivar magic: Used to ensure the connection goes to the right disk
1825 @ivar ipv6: Whether to use IPv6
1826 @ivar connect_timeout: Number of seconds for establishing connection
1839 class ConfdRequest(ConfigObject):
1840 """Object holding a confd request.
1842 @ivar protocol: confd protocol version
1843 @ivar type: confd query type
1844 @ivar query: query request
1845 @ivar rsalt: requested reply salt
1856 class ConfdReply(ConfigObject):
1857 """Object holding a confd reply.
1859 @ivar protocol: confd protocol version
1860 @ivar status: reply status code (ok, error)
1861 @ivar answer: confd query reply
1862 @ivar serial: configuration serial number
1873 class QueryFieldDefinition(ConfigObject):
1874 """Object holding a query field definition.
1876 @ivar name: Field name
1877 @ivar title: Human-readable title
1878 @ivar kind: Field type
1879 @ivar doc: Human-readable description
1890 class _QueryResponseBase(ConfigObject):
1896 """Custom function for serializing.
1899 mydict = super(_QueryResponseBase, self).ToDict()
1900 mydict["fields"] = objectutils.ContainerToDicts(mydict["fields"])
1904 def FromDict(cls, val):
1905 """Custom function for de-serializing.
1908 obj = super(_QueryResponseBase, cls).FromDict(val)
1910 objectutils.ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1914 class QueryResponse(_QueryResponseBase):
1915 """Object holding the response to a query.
1917 @ivar fields: List of L{QueryFieldDefinition} objects
1918 @ivar data: Requested data
1926 class QueryFieldsRequest(ConfigObject):
1927 """Object holding a request for querying available fields.
1936 class QueryFieldsResponse(_QueryResponseBase):
1937 """Object holding the response to a query for fields.
1939 @ivar fields: List of L{QueryFieldDefinition} objects
1945 class MigrationStatus(ConfigObject):
1946 """Object holding the status of a migration.
1956 class InstanceConsole(ConfigObject):
1957 """Object describing how to access the console of an instance.
1972 """Validates contents of this object.
1975 assert self.kind in constants.CONS_ALL, "Unknown console type"
1976 assert self.instance, "Missing instance name"
1977 assert self.message or self.kind in [constants.CONS_SSH,
1978 constants.CONS_SPICE,
1980 assert self.host or self.kind == constants.CONS_MESSAGE
1981 assert self.port or self.kind in [constants.CONS_MESSAGE,
1983 assert self.user or self.kind in [constants.CONS_MESSAGE,
1984 constants.CONS_SPICE,
1986 assert self.command or self.kind in [constants.CONS_MESSAGE,
1987 constants.CONS_SPICE,
1989 assert self.display or self.kind in [constants.CONS_MESSAGE,
1990 constants.CONS_SPICE,
1995 class Network(TaggableObject):
1996 """Object representing a network definition for ganeti.
2012 ] + _TIMESTAMPS + _UUID
2015 class SerializableConfigParser(ConfigParser.SafeConfigParser):
2016 """Simple wrapper over ConfigParse that allows serialization.
2018 This class is basically ConfigParser.SafeConfigParser with two
2019 additional methods that allow it to serialize/unserialize to/from a
2024 """Dump this instance and return the string representation."""
2027 return buf.getvalue()
2030 def Loads(cls, data):
2031 """Load data from a string."""
2032 buf = StringIO(data)
2038 class LvmPvInfo(ConfigObject):
2039 """Information about an LVM physical volume (PV).
2042 @ivar name: name of the PV
2043 @type vg_name: string
2044 @ivar vg_name: name of the volume group containing the PV
2046 @ivar size: size of the PV in MiB
2048 @ivar free: free space in the PV, in MiB
2049 @type attributes: string
2050 @ivar attributes: PV attributes
2051 @type lv_list: list of strings
2052 @ivar lv_list: names of the LVs hosted on the PV
2064 """Is this PV empty?
2067 return self.size <= (self.free + 1)
2069 def IsAllocatable(self):
2070 """Is this PV allocatable?
2073 return ("a" in self.attributes)