X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/96acbc09b70c82051e2156744bf1ae6752004cb7..3ddd94f919e69a30ab2c495070a7d01617a5e3a2:/lib/objects.py diff --git a/lib/objects.py b/lib/objects.py index 1cdc98f..7be9e71 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,33 +26,53 @@ pass to and from external parties. """ +# pylint: disable-msg=E0203,W0201 + +# E0203: Access to member %r before its definition, since we use +# objects.py which doesn't explicitely initialise its members + +# W0201: Attribute '%s' defined outside __init__ import ConfigParser import re import copy +import time from cStringIO import StringIO from ganeti import errors from ganeti import constants +from socket import AF_INET + __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", - "OS", "Node", "Cluster", "FillDict"] + "OS", "Node", "NodeGroup", "Cluster", "FillDict"] +_TIMESTAMPS = ["ctime", "mtime"] +_UUID = ["uuid"] -def FillDict(defaults_dict, custom_dict): + +def FillDict(defaults_dict, custom_dict, skip_keys=None): """Basic function to apply settings on top a default dict. @type defaults_dict: dict @param defaults_dict: dictionary holding the default values @type custom_dict: dict @param custom_dict: dictionary holding customized value + @type skip_keys: list + @param skip_keys: which keys not to fill @rtype: dict @return: dict with the 'full' values """ ret_dict = copy.deepcopy(defaults_dict) ret_dict.update(custom_dict) + if skip_keys: + for k in skip_keys: + try: + del ret_dict[k] + except KeyError: + pass return ret_dict @@ -91,31 +111,29 @@ class ConfigObject(object): def __init__(self, **kwargs): for k, v in kwargs.iteritems(): setattr(self, k, v) - self.UpgradeConfig() def __getattr__(self, name): - if name not in self.__slots__: + if name not in self._all_slots(): raise AttributeError("Invalid object attribute %s.%s" % (type(self).__name__, name)) return None - def __setitem__(self, key, value): - if key not in self.__slots__: - raise KeyError(key) - setattr(self, key, value) - - def __getstate__(self): - state = {} - for name in self.__slots__: - if hasattr(self, name): - state[name] = getattr(self, name) - return state - def __setstate__(self, state): + slots = self._all_slots() for name in state: - if name in self.__slots__: + if name in slots: setattr(self, name, state[name]) + @classmethod + def _all_slots(cls): + """Compute the list of all declared slots for a class. + + """ + slots = [] + for parent in cls.__mro__: + slots.extend(getattr(parent, "__slots__", [])) + return slots + def ToDict(self): """Convert to a dict holding only standard python types. @@ -126,7 +144,14 @@ class ConfigObject(object): make sure all objects returned are only standard python types. """ - return dict([(k, getattr(self, k, None)) for k in self.__slots__]) + result = {} + for name in self._all_slots(): + value = getattr(self, name, None) + if value is not None: + result[name] = value + return result + + __getstate__ = ToDict @classmethod def FromDict(cls, val): @@ -145,7 +170,7 @@ class ConfigObject(object): raise errors.ConfigurationError("Invalid object passed to FromDict:" " expected dict, got %s" % type(val)) val_str = dict([(str(k), v) for k, v in val.iteritems()]) - obj = cls(**val_str) + obj = cls(**val_str) # pylint: disable-msg=W0142 return obj @staticmethod @@ -178,6 +203,8 @@ class ConfigObject(object): if not isinstance(c_type, type): raise TypeError("Container type %s passed to _ContainerFromDicts is" " not a type" % type(c_type)) + if source is None: + source = c_type() if c_type is dict: ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()]) elif c_type in (list, tuple, set, frozenset): @@ -187,6 +214,14 @@ class ConfigObject(object): " _ContainerFromDicts" % c_type) return ret + def Copy(self): + """Makes a deep copy of the current object and its children. + + """ + dict_form = self.ToDict() + clone_obj = self.__class__.FromDict(dict_form) + return clone_obj + def __repr__(self): """Implement __repr__ for ConfigObjects.""" return repr(self.ToDict()) @@ -194,8 +229,8 @@ class ConfigObject(object): def UpgradeConfig(self): """Fill defaults for missing configuration values. - This method will be called at object init time, and its implementation will - be object dependent. + This method will be called at configuration load time, and its + implementation will be object dependent. """ pass @@ -205,10 +240,11 @@ class TaggableObject(ConfigObject): """An generic class supporting tags. """ - __slots__ = ConfigObject.__slots__ + ["tags"] + __slots__ = ["tags"] + VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$") - @staticmethod - def ValidateTag(tag): + @classmethod + def ValidateTag(cls, tag): """Check if a tag is valid. If the tag is invalid, an errors.TagError will be raised. The @@ -222,7 +258,7 @@ class TaggableObject(ConfigObject): constants.MAX_TAG_LEN) if not tag: raise errors.TagError("Tags cannot be empty") - if not re.match("^[\w.+*/:-]+$", tag): + if not cls.VALID_TAG_RE.match(tag): raise errors.TagError("Tag contains invalid characters") def GetTags(self): @@ -281,7 +317,14 @@ class TaggableObject(ConfigObject): class ConfigData(ConfigObject): """Top-level config object.""" - __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"] + __slots__ = [ + "version", + "cluster", + "nodes", + "nodegroups", + "instances", + "serial_no", + ] + _TIMESTAMPS def ToDict(self): """Custom function for top-level config data. @@ -292,7 +335,7 @@ class ConfigData(ConfigObject): """ mydict = super(ConfigData, self).ToDict() mydict["cluster"] = mydict["cluster"].ToDict() - for key in "nodes", "instances": + for key in "nodes", "instances", "nodegroups": mydict[key] = self._ContainerToDicts(mydict[key]) return mydict @@ -306,12 +349,48 @@ class ConfigData(ConfigObject): obj.cluster = Cluster.FromDict(obj.cluster) obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node) obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance) + obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup) return obj + def HasAnyDiskOfType(self, dev_type): + """Check if in there is at disk of the given type in the configuration. + + @type dev_type: L{constants.LDS_BLOCK} + @param dev_type: the type to look for + @rtype: boolean + @return: boolean indicating if a disk of the given type was found or not + + """ + for instance in self.instances.values(): + for disk in instance.disks: + if disk.IsBasedOnDiskType(dev_type): + return True + return False + + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + """ + self.cluster.UpgradeConfig() + for node in self.nodes.values(): + node.UpgradeConfig() + for instance in self.instances.values(): + instance.UpgradeConfig() + if self.nodegroups is None: + self.nodegroups = {} + for nodegroup in self.nodegroups.values(): + nodegroup.UpgradeConfig() + if self.cluster.drbd_usermode_helper is None: + # To decide if we set an helper let's check if at least one instance has + # a DRBD disk. This does not cover all the possible scenarios but it + # gives a good approximation. + if self.HasAnyDiskOfType(constants.LD_DRBD8): + self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER + class NIC(ConfigObject): """Config object representing a network card.""" - __slots__ = ["mac", "ip", "bridge", "nicparams"] + __slots__ = ["mac", "ip", "nicparams"] @classmethod def CheckParameterSyntax(cls, nicparams): @@ -326,25 +405,11 @@ class NIC(ConfigObject): err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE] raise errors.ConfigurationError(err) - if (nicparams[constants.NIC_MODE] is constants.NIC_MODE_BRIDGED and + if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and not nicparams[constants.NIC_LINK]): err = "Missing bridged nic link" raise errors.ConfigurationError(err) - def UpgradeConfig(self): - """Fill defaults for missing configuration values. - - """ - if self.nicparams is None: - self.nicparams = {} - if self.bridge is not None: - self.nicparams[constants.NIC_MODE] = constants.NIC_MODE_BRIDGED - self.nicparams[constants.NIC_LINK] = self.bridge - # bridge is no longer used it 2.1. The slot is left there to support - # upgrading, but will be removed in 2.2 - if self.bridge is not None: - self.bridge = None - class Disk(ConfigObject): """Config object representing a block device.""" @@ -370,9 +435,14 @@ class Disk(ConfigObject): irrespective of their status. For such devices, we return this path, for others we return None. + @warning: The path returned is not a normalized pathname; callers + should check that it is a valid path. + """ if self.dev_type == constants.LD_LV: return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) + elif self.dev_type == constants.LD_BLOCKDEV: + return self.logical_id[1] return None def ChildrenNeeded(self): @@ -391,6 +461,21 @@ class Disk(ConfigObject): return 0 return -1 + def IsBasedOnDiskType(self, dev_type): + """Check if the disk or its children are based on the given type. + + @type dev_type: L{constants.LDS_BLOCK} + @param dev_type: the type to look for + @rtype: boolean + @return: boolean indicating if a device of the given type was found or not + + """ + if self.children: + for child in self.children: + if child.IsBasedOnDiskType(dev_type): + return True + return self.dev_type == dev_type + def GetNodes(self, node): """This function returns the nodes this device lives on. @@ -400,7 +485,8 @@ class Disk(ConfigObject): devices needs to (or can) be assembled. """ - if self.dev_type in [constants.LD_LV, constants.LD_FILE]: + if self.dev_type in [constants.LD_LV, constants.LD_FILE, + constants.LD_BLOCKDEV]: result = [node] elif self.dev_type in constants.LDS_DRBD: result = [self.logical_id[0], self.logical_id[1]] @@ -445,6 +531,28 @@ class Disk(ConfigObject): # be different) return result + def ComputeGrowth(self, amount): + """Compute the per-VG growth requirements. + + This only works for VG-based disks. + + @type amount: integer + @param amount: the desired increase in (user-visible) disk space + @rtype: dict + @return: a dictionary of volume-groups and the required size + + """ + if self.dev_type == constants.LD_LV: + return {self.logical_id[0]: amount} + elif self.dev_type == constants.LD_DRBD8: + if self.children: + return self.children[0].ComputeGrowth(amount) + else: + return {} + else: + # Other disk types do not require VG space + return {} + def RecordGrow(self, amount): """Update the size of this disk after growth. @@ -453,7 +561,7 @@ class Disk(ConfigObject): actual algorithms from bdev. """ - if self.dev_type == constants.LD_LV: + if self.dev_type in (constants.LD_LV, constants.LD_FILE): self.size += amount elif self.dev_type == constants.LD_DRBD8: if self.children: @@ -463,6 +571,15 @@ class Disk(ConfigObject): raise errors.ProgrammerError("Disk.RecordGrow called for unsupported" " disk type %s" % self.dev_type) + def UnsetSize(self): + """Sets recursively the size to zero for the disk and its children. + + """ + if self.children: + for child in self.children: + child.UnsetSize() + self.size = 0 + def SetPhysicalID(self, target_node, nodes_ip): """Convert the logical ID to the physical ID. @@ -584,23 +701,33 @@ class Disk(ConfigObject): all_errors.append("Disk access mode '%s' is invalid" % (self.mode, )) return all_errors + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + """ + if self.children: + for child in self.children: + child.UpgradeConfig() + # add here config upgrade for this disk + class Instance(TaggableObject): """Config object representing an instance.""" - __slots__ = TaggableObject.__slots__ + [ + __slots__ = [ "name", "primary_node", "os", "hypervisor", "hvparams", "beparams", + "osparams", "admin_up", "nics", "disks", "disk_template", "network_port", "serial_no", - ] + ] + _TIMESTAMPS + _UUID def _ComputeSecondaryNodes(self): """Compute the list of secondary nodes. @@ -652,8 +779,10 @@ class Instance(TaggableObject): @param lvmap: optional dictionary to receive the 'node' : ['lv', ...] data. - @return: None if lvmap arg is given, otherwise, a dictionary - of the form { 'nodename' : ['volume1', 'volume2', ...], ... } + @return: None if lvmap arg is given, otherwise, a dictionary of + the form { 'nodename' : ['volume1', 'volume2', ...], ... }; + volumeN is of the form "vg_name/lv_name", compatible with + GetVolumeList() """ if node == None: @@ -672,7 +801,7 @@ class Instance(TaggableObject): for dev in devs: if dev.dev_type == constants.LD_LV: - lvmap[node].append(dev.logical_id[1]) + lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1]) elif dev.dev_type in constants.LDS_DRBD: if dev.children: @@ -699,11 +828,13 @@ class Instance(TaggableObject): try: idx = int(idx) return self.disks[idx] - except ValueError, err: - raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err)) + except (TypeError, ValueError), err: + raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err), + errors.ECODE_INVAL) except IndexError: raise errors.OpPrereqError("Invalid disk index: %d (instace has disks" - " 0 to %d" % (idx, len(self.disks))) + " 0 to %d" % (idx, len(self.disks) - 1), + errors.ECODE_INVAL) def ToDict(self): """Instance-specific conversion to standard python types. @@ -733,9 +864,35 @@ class Instance(TaggableObject): obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk) return obj + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + """ + for nic in self.nics: + nic.UpgradeConfig() + for disk in self.disks: + disk.UpgradeConfig() + if self.hvparams: + for key in constants.HVC_GLOBALS: + try: + del self.hvparams[key] + except KeyError: + pass + if self.osparams is None: + self.osparams = {} + class OS(ConfigObject): - """Config object representing an operating system.""" + """Config object representing an operating system. + + @type supported_parameters: list + @ivar supported_parameters: a list of tuples, name and description, + containing the supported parameters by this OS + + @type VARIANT_DELIM: string + @cvar VARIANT_DELIM: the variant delimiter + + """ __slots__ = [ "name", "path", @@ -744,12 +901,50 @@ class OS(ConfigObject): "export_script", "import_script", "rename_script", + "verify_script", + "supported_variants", + "supported_parameters", ] + VARIANT_DELIM = "+" + + @classmethod + def SplitNameVariant(cls, name): + """Splits the name into the proper name and variant. + + @param name: the OS (unprocessed) name + @rtype: list + @return: a list of two elements; if the original name didn't + contain a variant, it's returned as an empty string + + """ + nv = name.split(cls.VARIANT_DELIM, 1) + if len(nv) == 1: + nv.append("") + return nv + + @classmethod + def GetName(cls, name): + """Returns the proper name of the os (without the variant). + + @param name: the OS (unprocessed) name + + """ + return cls.SplitNameVariant(name)[0] + + @classmethod + def GetVariant(cls, name): + """Returns the variant the os (without the base name). + + @param name: the OS (unprocessed) name + + """ + return cls.SplitNameVariant(name)[1] + class Node(TaggableObject): """Config object representing a node.""" - __slots__ = TaggableObject.__slots__ + [ + __slots__ = [ "name", "primary_ip", "secondary_ip", @@ -757,18 +952,116 @@ class Node(TaggableObject): "master_candidate", "offline", "drained", - ] + "group", + "master_capable", + "vm_capable", + "ndparams", + "powered", + ] + _TIMESTAMPS + _UUID + + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + """ + # pylint: disable-msg=E0203 + # because these are "defined" via slots, not manually + if self.master_capable is None: + self.master_capable = True + + if self.vm_capable is None: + self.vm_capable = True + + if self.ndparams is None: + self.ndparams = {} + + if self.powered is None: + self.powered = True + + +class NodeGroup(TaggableObject): + """Config object representing a node group.""" + __slots__ = [ + "name", + "members", + "ndparams", + "serial_no", + "alloc_policy", + ] + _TIMESTAMPS + _UUID + + def ToDict(self): + """Custom function for nodegroup. + + This discards the members object, which gets recalculated and is only kept + in memory. + + """ + mydict = super(NodeGroup, self).ToDict() + del mydict["members"] + return mydict + + @classmethod + def FromDict(cls, val): + """Custom function for nodegroup. + + The members slot is initialized to an empty list, upon deserialization. + + """ + obj = super(NodeGroup, cls).FromDict(val) + obj.members = [] + return obj + + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + """ + if self.ndparams is None: + self.ndparams = {} + + if self.serial_no is None: + self.serial_no = 1 + + if self.alloc_policy is None: + self.alloc_policy = constants.ALLOC_POLICY_PREFERRED + + # We only update mtime, and not ctime, since we would not be able to provide + # a correct value for creation time. + if self.mtime is None: + self.mtime = time.time() + + def FillND(self, node): + """Return filled out ndparams for L{object.Node} + + @type node: L{objects.Node} + @param node: A Node object to fill + @return a copy of the node's ndparams with defaults filled + + """ + return self.SimpleFillND(node.ndparams) + + def SimpleFillND(self, ndparams): + """Fill a given ndparams dict with defaults. + + @type ndparams: dict + @param ndparams: the dict to fill + @rtype: dict + @return: a copy of the passed in ndparams with missing keys filled + from the node group defaults + + """ + return FillDict(self.ndparams, ndparams) class Cluster(TaggableObject): """Config object representing the cluster.""" - __slots__ = TaggableObject.__slots__ + [ + __slots__ = [ "serial_no", "rsahostkeypub", "highest_used_port", "tcpudp_port_pool", "mac_prefix", "volume_group_name", + "reserved_lvs", + "drbd_usermode_helper", "default_bridge", "default_hypervisor", "master_node", @@ -776,18 +1069,32 @@ class Cluster(TaggableObject): "master_netdev", "cluster_name", "file_storage_dir", + "shared_file_storage_dir", "enabled_hypervisors", "hvparams", + "os_hvp", "beparams", + "osparams", "nicparams", + "ndparams", "candidate_pool_size", "modify_etc_hosts", - ] + "modify_ssh_setup", + "maintain_node_health", + "uid_pool", + "default_iallocator", + "hidden_os", + "blacklisted_os", + "primary_ip_family", + "prealloc_wipe_disks", + ] + _TIMESTAMPS + _UUID def UpgradeConfig(self): """Fill defaults for missing configuration values. """ + # pylint: disable-msg=E0203 + # because these are "defined" via slots, not manually if self.hvparams is None: self.hvparams = constants.HVC_DEFAULTS else: @@ -795,6 +1102,16 @@ class Cluster(TaggableObject): self.hvparams[hypervisor] = FillDict( constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor]) + if self.os_hvp is None: + self.os_hvp = {} + + # osparams added before 2.2 + if self.osparams is None: + self.osparams = {} + + if self.ndparams is None: + self.ndparams = constants.NDC_DEFAULTS + self.beparams = UpgradeGroupedParams(self.beparams, constants.BEC_DEFAULTS) migrate_default_bridge = not self.nicparams @@ -807,18 +1124,54 @@ class Cluster(TaggableObject): if self.modify_etc_hosts is None: self.modify_etc_hosts = True - # default_bridge is no longer used it 2.1. The slot is left there to - # support auto-upgrading, but will be removed in 2.2 + if self.modify_ssh_setup is None: + self.modify_ssh_setup = True + + # default_bridge is no longer used in 2.1. The slot is left there to + # support auto-upgrading. It can be removed once we decide to deprecate + # upgrading straight from 2.0. if self.default_bridge is not None: self.default_bridge = None - # default_hypervisor is just the first enabled one in 2.1 + # default_hypervisor is just the first enabled one in 2.1. This slot and + # code can be removed once upgrading straight from 2.0 is deprecated. if self.default_hypervisor is not None: - self.enabled_hypervisors = [self.default_hypervisor] + \ + self.enabled_hypervisors = ([self.default_hypervisor] + [hvname for hvname in self.enabled_hypervisors - if hvname != self.default_hypervisor] + if hvname != self.default_hypervisor]) self.default_hypervisor = None + # maintain_node_health added after 2.1.1 + if self.maintain_node_health is None: + self.maintain_node_health = False + + if self.uid_pool is None: + self.uid_pool = [] + + if self.default_iallocator is None: + self.default_iallocator = "" + + # reserved_lvs added before 2.2 + if self.reserved_lvs is None: + self.reserved_lvs = [] + + # hidden and blacklisted operating systems added before 2.2.1 + if self.hidden_os is None: + self.hidden_os = [] + + if self.blacklisted_os is None: + self.blacklisted_os = [] + + # primary_ip_family added before 2.3 + if self.primary_ip_family is None: + self.primary_ip_family = AF_INET + + if self.prealloc_wipe_disks is None: + self.prealloc_wipe_disks = False + + # shared_file_storage_dir added before 2.5 + if self.shared_file_storage_dir is None: + self.shared_file_storage_dir = "" def ToDict(self): """Custom function for cluster. @@ -838,21 +1191,82 @@ class Cluster(TaggableObject): obj.tcpudp_port_pool = set(obj.tcpudp_port_pool) return obj - def FillHV(self, instance): - """Fill an instance's hvparams dict. + def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None): + """Get the default hypervisor parameters for the cluster. + + @param hypervisor: the hypervisor name + @param os_name: if specified, we'll also update the defaults for this OS + @param skip_keys: if passed, list of keys not to use + @return: the defaults dict + + """ + if skip_keys is None: + skip_keys = [] + + fill_stack = [self.hvparams.get(hypervisor, {})] + if os_name is not None: + os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {}) + fill_stack.append(os_hvp) + + ret_dict = {} + for o_dict in fill_stack: + ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys) + + return ret_dict + + def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False): + """Fill a given hvparams dict with cluster defaults. + + @type hv_name: string + @param hv_name: the hypervisor to use + @type os_name: string + @param os_name: the OS to use for overriding the hypervisor defaults + @type skip_globals: boolean + @param skip_globals: if True, the global hypervisor parameters will + not be filled + @rtype: dict + @return: a copy of the given hvparams with missing keys filled from + the cluster defaults + + """ + if skip_globals: + skip_keys = constants.HVC_GLOBALS + else: + skip_keys = [] + + def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys) + return FillDict(def_dict, hvparams, skip_keys=skip_keys) + + def FillHV(self, instance, skip_globals=False): + """Fill an instance's hvparams dict with cluster defaults. @type instance: L{objects.Instance} @param instance: the instance parameter to fill + @type skip_globals: boolean + @param skip_globals: if True, the global hypervisor parameters will + not be filled @rtype: dict @return: a copy of the instance's hvparams with missing keys filled from the cluster defaults """ - return FillDict(self.hvparams.get(instance.hypervisor, {}), - instance.hvparams) + return self.SimpleFillHV(instance.hypervisor, instance.os, + instance.hvparams, skip_globals) + + def SimpleFillBE(self, beparams): + """Fill a given beparams dict with cluster defaults. + + @type beparams: dict + @param beparams: the dict to fill + @rtype: dict + @return: a copy of the passed in beparams with missing keys filled + from the cluster defaults + + """ + return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams) def FillBE(self, instance): - """Fill an instance's beparams dict. + """Fill an instance's beparams dict with cluster defaults. @type instance: L{objects.Instance} @param instance: the instance parameter to fill @@ -861,8 +1275,63 @@ class Cluster(TaggableObject): the cluster defaults """ - return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), - instance.beparams) + return self.SimpleFillBE(instance.beparams) + + def SimpleFillNIC(self, nicparams): + """Fill a given nicparams dict with cluster defaults. + + @type nicparams: dict + @param nicparams: the dict to fill + @rtype: dict + @return: a copy of the passed in nicparams with missing keys filled + from the cluster defaults + + """ + return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams) + + def SimpleFillOS(self, os_name, os_params): + """Fill an instance's osparams dict with cluster defaults. + + @type os_name: string + @param os_name: the OS name to use + @type os_params: dict + @param os_params: the dict to fill with default values + @rtype: dict + @return: a copy of the instance's osparams with missing keys filled from + the cluster defaults + + """ + name_only = os_name.split("+", 1)[0] + # base OS + result = self.osparams.get(name_only, {}) + # OS with variant + result = FillDict(result, self.osparams.get(os_name, {})) + # specified params + return FillDict(result, os_params) + + def FillND(self, node, nodegroup): + """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node} + + @type node: L{objects.Node} + @param node: A Node object to fill + @type nodegroup: L{objects.NodeGroup} + @param nodegroup: A Node object to fill + @return a copy of the node's ndparams with defaults filled + + """ + return self.SimpleFillND(nodegroup.FillND(node)) + + def SimpleFillND(self, ndparams): + """Fill a given ndparams dict with defaults. + + @type ndparams: dict + @param ndparams: the dict to fill + @rtype: dict + @return: a copy of the passed in ndparams with missing keys filled + from the cluster defaults + + """ + return FillDict(self.ndparams, ndparams) class BlockDevStatus(ConfigObject): @@ -874,16 +1343,195 @@ class BlockDevStatus(ConfigObject): "sync_percent", "estimated_time", "is_degraded", - "ldisk_degraded", + "ldisk_status", + ] + + +class ImportExportStatus(ConfigObject): + """Config object representing the status of an import or export.""" + __slots__ = [ + "recent_output", + "listen_port", + "connected", + "progress_mbytes", + "progress_throughput", + "progress_eta", + "progress_percent", + "exit_status", + "error_message", + ] + _TIMESTAMPS + + +class ImportExportOptions(ConfigObject): + """Options for import/export daemon + + @ivar key_name: X509 key name (None for cluster certificate) + @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate) + @ivar compress: Compression method (one of L{constants.IEC_ALL}) + @ivar magic: Used to ensure the connection goes to the right disk + @ivar ipv6: Whether to use IPv6 + @ivar connect_timeout: Number of seconds for establishing connection + + """ + __slots__ = [ + "key_name", + "ca_pem", + "compress", + "magic", + "ipv6", + "connect_timeout", + ] + + +class ConfdRequest(ConfigObject): + """Object holding a confd request. + + @ivar protocol: confd protocol version + @ivar type: confd query type + @ivar query: query request + @ivar rsalt: requested reply salt + + """ + __slots__ = [ + "protocol", + "type", + "query", + "rsalt", ] - def ToLegacyStatus(self): - """Converts the device status to a legacy tuple. + +class ConfdReply(ConfigObject): + """Object holding a confd reply. + + @ivar protocol: confd protocol version + @ivar status: reply status code (ok, error) + @ivar answer: confd query reply + @ivar serial: configuration serial number + + """ + __slots__ = [ + "protocol", + "status", + "answer", + "serial", + ] + + +class QueryFieldDefinition(ConfigObject): + """Object holding a query field definition. + + @ivar name: Field name + @ivar title: Human-readable title + @ivar kind: Field type + @ivar doc: Human-readable description + + """ + __slots__ = [ + "name", + "title", + "kind", + "doc", + ] + + +class _QueryResponseBase(ConfigObject): + __slots__ = [ + "fields", + ] + + def ToDict(self): + """Custom function for serializing. + + """ + mydict = super(_QueryResponseBase, self).ToDict() + mydict["fields"] = self._ContainerToDicts(mydict["fields"]) + return mydict + + @classmethod + def FromDict(cls, val): + """Custom function for de-serializing. """ - return (self.dev_path, self.major, self.minor, - self.sync_percent, self.estimated_time, - self.is_degraded, self.ldisk_degraded) + obj = super(_QueryResponseBase, cls).FromDict(val) + obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition) + return obj + + +class QueryRequest(ConfigObject): + """Object holding a query request. + + """ + __slots__ = [ + "what", + "fields", + "filter", + ] + + +class QueryResponse(_QueryResponseBase): + """Object holding the response to a query. + + @ivar fields: List of L{QueryFieldDefinition} objects + @ivar data: Requested data + + """ + __slots__ = [ + "data", + ] + + +class QueryFieldsRequest(ConfigObject): + """Object holding a request for querying available fields. + + """ + __slots__ = [ + "what", + "fields", + ] + + +class QueryFieldsResponse(_QueryResponseBase): + """Object holding the response to a query for fields. + + @ivar fields: List of L{QueryFieldDefinition} objects + + """ + __slots__ = [ + ] + + +class InstanceConsole(ConfigObject): + """Object describing how to access the console of an instance. + + """ + __slots__ = [ + "instance", + "kind", + "message", + "host", + "port", + "user", + "command", + "display", + ] + + def Validate(self): + """Validates contents of this object. + + """ + assert self.kind in constants.CONS_ALL, "Unknown console type" + assert self.instance, "Missing instance name" + assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC] + assert self.host or self.kind == constants.CONS_MESSAGE + assert self.port or self.kind in [constants.CONS_MESSAGE, + constants.CONS_SSH] + assert self.user or self.kind in [constants.CONS_MESSAGE, + constants.CONS_VNC] + assert self.command or self.kind in [constants.CONS_MESSAGE, + constants.CONS_VNC] + assert self.display or self.kind in [constants.CONS_MESSAGE, + constants.CONS_SSH] + return True class SerializableConfigParser(ConfigParser.SafeConfigParser): @@ -900,10 +1548,10 @@ class SerializableConfigParser(ConfigParser.SafeConfigParser): self.write(buf) return buf.getvalue() - @staticmethod - def Loads(data): + @classmethod + def Loads(cls, data): """Load data from a string.""" buf = StringIO(data) - cfp = SerializableConfigParser() + cfp = cls() cfp.readfp(buf) return cfp