X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/65a153365a2270ed9599301c77ccd7afe1561226..4fe80ef2ed1cda3a6357274eccafe5c1f21a5283:/lib/objects.py?ds=sidebyside diff --git a/lib/objects.py b/lib/objects.py index 5427418..43cea75 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -27,9 +27,9 @@ pass to and from external parties. """ -import simplejson import ConfigParser import re +import copy from cStringIO import StringIO from ganeti import errors @@ -40,14 +40,6 @@ __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", "OS", "Node", "Cluster"] -# Check whether the simplejson module supports indentation -_JSON_INDENT = 2 -try: - simplejson.dumps(1, indent=_JSON_INDENT) -except TypeError: - _JSON_INDENT = None - - class ConfigObject(object): """A generic config object. @@ -58,7 +50,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__ = [] @@ -90,34 +82,6 @@ class ConfigObject(object): if name in self.__slots__: setattr(self, name, state[name]) - def Dump(self, fobj): - """Dump to a file object. - - """ - data = self.ToDict() - if _JSON_INDENT is None: - simplejson.dump(data, fobj) - else: - simplejson.dump(data, fobj, indent=_JSON_INDENT) - - @classmethod - def Load(cls, fobj): - """Load data from the given stream. - - """ - return cls.FromDict(simplejson.load(fobj)) - - def Dumps(self): - """Dump and return the string representation.""" - buf = StringIO() - self.Dump(buf) - return buf.getvalue() - - @classmethod - def Loads(cls, data): - """Load data from a string.""" - return cls.Load(StringIO(data)) - def ToDict(self): """Convert to a dict holding only standard python types. @@ -189,10 +153,27 @@ 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. @@ -215,7 +196,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 re.match("^[\w.+*/:-]+$", tag): raise errors.TagError("Tag contains invalid characters") def GetTags(self): @@ -274,7 +255,7 @@ class TaggableObject(ConfigObject): class ConfigData(ConfigObject): """Top-level config object.""" - __slots__ = ["cluster", "nodes", "instances"] + __slots__ = ["version", "cluster", "nodes", "instances", "serial_no"] def ToDict(self): """Custom function for top-level config data. @@ -301,6 +282,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.""" @@ -310,22 +301,48 @@ class NIC(ConfigObject): 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.""" - return self.dev_type in (constants.LD_DRBD7, constants.LD_DRBD8, - constants.LD_LV) + return self.dev_type in (constants.LD_DRBD8, constants.LD_LV) def AssembleOnSecondary(self): """Test if this device needs to be assembled on a secondary node.""" - return self.dev_type in (constants.LD_DRBD7, constants.LD_DRBD8, - constants.LD_LV) + return self.dev_type in (constants.LD_DRBD8, constants.LD_LV) def OpenOnSecondary(self): """Test if this device needs to be opened on a secondary node.""" return self.dev_type in (constants.LD_LV,) + def StaticDevPath(self): + """Return the device path if this device type has a static one. + + Some devices (LVM for example) live always at the same /dev/ path, + irrespective of their status. For such devices, we return this + path, for others we return None. + + """ + if self.dev_type == constants.LD_LV: + return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) + return None + + def ChildrenNeeded(self): + """Compute the needed number of children for activation. + + This method will return either -1 (all children) or a positive + number denoting the minimum number of children needed for + activation (only mirrored devices will usually return >=0). + + Currently, only DRBD8 supports diskless activation (therefore we + return 0), for all other we keep the previous semantics and return + -1. + + """ + if self.dev_type == constants.LD_DRBD8: + return 0 + return -1 + def GetNodes(self, node): """This function returns the nodes this device lives on. @@ -335,7 +352,7 @@ class Disk(ConfigObject): devices needs to (or can) be assembled. """ - if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_MD_R1: + if self.dev_type in [constants.LD_LV, constants.LD_FILE]: result = [node] elif self.dev_type in constants.LDS_DRBD: result = [self.logical_id[0], self.logical_id[1]] @@ -350,12 +367,9 @@ class Disk(ConfigObject): This method, given the node on which the parent disk lives, will return the list of all (node, disk) pairs which describe the disk - tree in the most compact way. For example, a md/drbd/lvm stack - will be returned as (primary_node, md) and (secondary_node, drbd) - which represents all the top-level devices on the nodes. This - means that on the primary node we need to activate the the md (and - recursively all its children) and on the secondary node we need to - activate the drbd device (and its children, the two lvm volumes). + tree in the most compact way. For example, a drbd/lvm stack + will be returned as (primary_node, drbd) and (secondary_node, drbd) + which represents all the top-level devices on the nodes. """ my_nodes = self.GetNodes(parent_node) @@ -383,6 +397,77 @@ class Disk(ConfigObject): # be different) return result + def RecordGrow(self, amount): + """Update the size of this disk after growth. + + This method recurses over the disks's children and updates their + size correspondigly. The method needs to be kept in sync with the + actual algorithms from bdev. + + """ + if self.dev_type == constants.LD_LV: + self.size += amount + elif self.dev_type == constants.LD_DRBD8: + if self.children: + self.children[0].RecordGrow(amount) + self.size += amount + else: + 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. + + This is used only for drbd, which needs ip/port configuration. + + The routine descends down and updates its children also, because + this helps when the only the top device is passed to the remote + node. + + Arguments: + - target_node: the node we wish to configure for + - nodes_ip: a mapping of node name to ip + + The target_node must exist in in nodes_ip, and must be one of the + nodes in the logical ID for each of the DRBD devices encountered + in the disk tree. + + """ + if self.children: + for child in self.children: + child.SetPhysicalID(target_node, nodes_ip) + + if self.logical_id is None and self.physical_id is not None: + return + if self.dev_type in constants.LDS_DRBD: + pnode, snode, port, pminor, sminor, secret = self.logical_id + if target_node not in (pnode, snode): + raise errors.ConfigurationError("DRBD device not knowing node %s" % + target_node) + pnode_ip = nodes_ip.get(pnode, None) + snode_ip = nodes_ip.get(snode, None) + if pnode_ip is None or snode_ip is None: + raise errors.ConfigurationError("Can't find primary or secondary node" + " for %s" % str(self)) + p_data = (pnode_ip, port) + s_data = (snode_ip, port) + if pnode == target_node: + self.physical_id = p_data + s_data + (pminor, secret) + else: # it must be secondary, we tested above + self.physical_id = s_data + p_data + (sminor, secret) + else: + self.physical_id = self.logical_id + return + def ToDict(self): """Disk-specific conversion to standard python types. @@ -410,6 +495,10 @@ class Disk(ConfigObject): obj.logical_id = tuple(obj.logical_id) if obj.physical_id and isinstance(obj.physical_id, list): obj.physical_id = tuple(obj.physical_id) + if obj.dev_type in constants.LDS_DRBD: + # we need a tuple of length six here + if len(obj.logical_id) < 6: + obj.logical_id += (None,) * (6 - len(obj.logical_id)) return obj def __str__(self): @@ -419,20 +508,21 @@ class Disk(ConfigObject): if self.dev_type == constants.LD_LV: val = "