X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/2b1ac2247d942c4c689ccb8f71d4d53d82be416a..791f317d1395440a966631c9fb9fa40a7c40e950:/lib/objects.py diff --git a/lib/objects.py b/lib/objects.py index 2e64a04..c92ea3f 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -26,6 +26,12 @@ 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 @@ -39,20 +45,32 @@ from ganeti import constants __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", "OS", "Node", "Cluster", "FillDict"] -def FillDict(defaults_dict, custom_dict): - """Basic function to apply settings on top a default dict. +_TIMESTAMPS = ["ctime", "mtime"] +_UUID = ["uuid"] - @type defaults_dict: dict - @param defaults_dict: dictionary holding the default values - @type custom_dict: dict - @param custom_dict: dictionary holding customized value - @rtype: dict - @return: dict with the 'full' values - """ - ret_dict = copy.deepcopy(defaults_dict) - ret_dict.update(custom_dict) - return ret_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 def UpgradeGroupedParams(target, defaults): @@ -90,31 +108,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. @@ -125,7 +141,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): @@ -144,7 +167,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 @@ -186,6 +209,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()) @@ -193,8 +224,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 @@ -204,10 +235,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 @@ -221,7 +253,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): @@ -280,7 +312,8 @@ class TaggableObject(ConfigObject): class ConfigData(ConfigObject): """Top-level config object.""" - __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"] + __slots__ = (["version", "cluster", "nodes", "instances", "serial_no"] + + _TIMESTAMPS) def ToDict(self): """Custom function for top-level config data. @@ -307,6 +340,16 @@ class ConfigData(ConfigObject): obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance) return obj + 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() + class NIC(ConfigObject): """Config object representing a network card.""" @@ -325,7 +368,7 @@ 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) @@ -369,6 +412,9 @@ 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]) @@ -462,6 +508,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. @@ -578,15 +633,24 @@ class Disk(ConfigObject): """Checks that this disk is correctly configured. """ - errors = [] + all_errors = [] if self.mode not in constants.DISK_ACCESS_SET: - errors.append("Disk access mode '%s' is invalid" % (self.mode, )) - return errors + 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", @@ -599,7 +663,7 @@ class Instance(TaggableObject): "disk_template", "network_port", "serial_no", - ] + ] + _TIMESTAMPS + _UUID def _ComputeSecondaryNodes(self): """Compute the list of secondary nodes. @@ -698,11 +762,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)), + errors.ECODE_INVAL) def ToDict(self): """Instance-specific conversion to standard python types. @@ -732,6 +798,21 @@ 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 + class OS(ConfigObject): """Config object representing an operating system.""" @@ -743,12 +824,13 @@ class OS(ConfigObject): "export_script", "import_script", "rename_script", + "supported_variants", ] class Node(TaggableObject): """Config object representing a node.""" - __slots__ = TaggableObject.__slots__ + [ + __slots__ = [ "name", "primary_ip", "secondary_ip", @@ -756,12 +838,12 @@ class Node(TaggableObject): "master_candidate", "offline", "drained", - ] + ] + _TIMESTAMPS + _UUID class Cluster(TaggableObject): """Config object representing the cluster.""" - __slots__ = TaggableObject.__slots__ + [ + __slots__ = [ "serial_no", "rsahostkeypub", "highest_used_port", @@ -777,16 +859,20 @@ class Cluster(TaggableObject): "file_storage_dir", "enabled_hypervisors", "hvparams", + "os_hvp", "beparams", "nicparams", "candidate_pool_size", "modify_etc_hosts", - ] + "modify_ssh_setup", + ] + _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: @@ -794,6 +880,10 @@ class Cluster(TaggableObject): self.hvparams[hypervisor] = FillDict( constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor]) + # TODO: Figure out if it's better to put this into OS than Cluster + if self.os_hvp is None: + self.os_hvp = {} + self.beparams = UpgradeGroupedParams(self.beparams, constants.BEC_DEFAULTS) migrate_default_bridge = not self.nicparams @@ -806,11 +896,21 @@ class Cluster(TaggableObject): if self.modify_etc_hosts is None: self.modify_etc_hosts = True + if self.modify_ssh_setup is None: + self.modify_ssh_setup = 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.default_bridge is not None: self.default_bridge = None + # default_hypervisor is just the first enabled one in 2.1 + if self.default_hypervisor is not None: + self.enabled_hypervisors = ([self.default_hypervisor] + + [hvname for hvname in self.enabled_hypervisors + if hvname != self.default_hypervisor]) + self.default_hypervisor = None + def ToDict(self): """Custom function for cluster. @@ -829,18 +929,36 @@ class Cluster(TaggableObject): obj.tcpudp_port_pool = set(obj.tcpudp_port_pool) return obj - def FillHV(self, instance): + def FillHV(self, instance, skip_globals=False): """Fill an instance's hvparams dict. @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) + if skip_globals: + skip_keys = constants.HVC_GLOBALS + else: + skip_keys = [] + + # We fill the list from least to most important override + fill_stack = [ + self.hvparams.get(instance.hypervisor, {}), + self.os_hvp.get(instance.os, {}).get(instance.hypervisor, {}), + instance.hvparams, + ] + + ret_dict = {} + for o_dict in fill_stack: + ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys) + + return ret_dict def FillBE(self, instance): """Fill an instance's beparams dict. @@ -853,7 +971,54 @@ class Cluster(TaggableObject): """ return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), - instance.beparams) + instance.beparams) + + +class BlockDevStatus(ConfigObject): + """Config object representing the status of a block device.""" + __slots__ = [ + "dev_path", + "major", + "minor", + "sync_percent", + "estimated_time", + "is_degraded", + "ldisk_status", + ] + + +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", + ] + + +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 SerializableConfigParser(ConfigParser.SafeConfigParser):