X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/5bf7b5cf4b09beb0bf3723af51c8aa06edcf015c..6bb65e3a3d84fb9ea3daeae596bbc9463edda8b8:/lib/objects.py?ds=sidebyside diff --git a/lib/objects.py b/lib/objects.py index 6e6667f..7fb7d5b 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 @@ -37,7 +43,50 @@ from ganeti import constants __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", - "OS", "Node", "Cluster"] + "OS", "Node", "Cluster", "FillDict"] + +_TIMESTAMPS = ["ctime", "mtime"] +_UUID = ["uuid"] + +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): + """Update all groups for the target parameter. + + @type target: dict of dicts + @param target: {group: {parameter: value}} + @type defaults: dict + @param defaults: default parameter values + + """ + if target is None: + target = {constants.PP_DEFAULT: defaults} + else: + for group in target: + target[group] = FillDict(defaults, target[group]) + return target class ConfigObject(object): @@ -50,7 +99,7 @@ class ConfigObject(object): as None instead of raising an error Classes derived from this must always declare __slots__ (we use many - config objects and the memory reduction is useful. + config objects and the memory reduction is useful) """ __slots__ = [] @@ -60,28 +109,27 @@ class ConfigObject(object): setattr(self, k, v) 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. @@ -92,7 +140,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): @@ -111,7 +166,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 @@ -153,19 +208,37 @@ 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()) + def UpgradeConfig(self): + """Fill defaults for missing configuration values. + + This method will be called at configuration load time, and its + implementation will be object dependent. + + """ + pass + 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 @@ -179,7 +252,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): @@ -238,7 +311,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. @@ -265,16 +339,58 @@ 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.""" - __slots__ = ["mac", "ip", "bridge"] + __slots__ = ["mac", "ip", "bridge", "nicparams"] + + @classmethod + def CheckParameterSyntax(cls, nicparams): + """Check the given parameters for validity. + + @type nicparams: dict + @param nicparams: dictionary with parameter names/value + @raise errors.ConfigurationError: when a parameter is not valid + + """ + if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES: + err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE] + raise errors.ConfigurationError(err) + + 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.""" __slots__ = ["dev_type", "logical_id", "physical_id", - "children", "iv_name", "size"] + "children", "iv_name", "size", "mode"] def CreateOnSecondary(self): """Test if this device needs to be created on a secondary node.""" @@ -388,6 +504,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. @@ -472,6 +597,7 @@ class Disk(ConfigObject): if self.dev_type == constants.LD_LV: val = "