X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/0fbedb7a33475b4c310992a19884f8e64bfd0533..3c286190a94c93de556e82fd2d84362b9e52a8d3:/lib/objects.py diff --git a/lib/objects.py b/lib/objects.py index 3554f29..dd83bd2 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 @@ -29,7 +29,7 @@ pass to and from external parties. # pylint: disable=E0203,W0201,R0902 # E0203: Access to member %r before its definition, since we use -# objects.py which doesn't explicitely initialise its members +# objects.py which doesn't explicitly initialise its members # W0201: Attribute '%s' defined outside __init__ @@ -44,12 +44,14 @@ from cStringIO import StringIO from ganeti import errors from ganeti import constants from ganeti import netutils +from ganeti import objectutils +from ganeti import utils from socket import AF_INET __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", - "OS", "Node", "NodeGroup", "Cluster", "FillDict"] + "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network"] _TIMESTAMPS = ["ctime", "mtime"] _UUID = ["uuid"] @@ -79,6 +81,39 @@ def FillDict(defaults_dict, custom_dict, skip_keys=None): return ret_dict +def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None): + """Fills an instance policy with defaults. + + """ + assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS + ret_dict = {} + for key in constants.IPOLICY_ISPECS: + ret_dict[key] = FillDict(default_ipolicy[key], + custom_ipolicy.get(key, {}), + skip_keys=skip_keys) + # list items + for key in [constants.IPOLICY_DTS]: + ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key])) + # other items which we know we can directly copy (immutables) + for key in constants.IPOLICY_PARAMETERS: + ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key]) + + return ret_dict + + +def FillDiskParams(default_dparams, custom_dparams, skip_keys=None): + """Fills the disk parameter defaults. + + @see: L{FillDict} for parameters and return value + + """ + assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES + + return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}), + skip_keys=skip_keys)) + for dt in constants.DISK_TEMPLATES) + + def UpgradeGroupedParams(target, defaults): """Update all groups for the target parameter. @@ -116,27 +151,48 @@ def UpgradeDiskParams(diskparams): @type diskparams: dict @param diskparams: disk parameters to upgrade @rtype: dict - @return: the upgraded disk parameters dit + @return: the upgraded disk parameters dict """ - result = dict() - if diskparams is None: - result = constants.DISK_DT_DEFAULTS.copy() + if not diskparams: + result = {} else: - # Update the disk parameter values for each disk template. - # The code iterates over constants.DISK_TEMPLATES because new templates - # might have been added. - for template in constants.DISK_TEMPLATES: - if template not in diskparams: - result[template] = constants.DISK_DT_DEFAULTS[template].copy() - else: - result[template] = FillDict(constants.DISK_DT_DEFAULTS[template], - diskparams[template]) + result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams) return result -class ConfigObject(object): +def UpgradeNDParams(ndparams): + """Upgrade ndparams structure. + + @type ndparams: dict + @param ndparams: disk parameters to upgrade + @rtype: dict + @return: the upgraded node parameters dict + + """ + if ndparams is None: + ndparams = {} + + if (constants.ND_OOB_PROGRAM in ndparams and + ndparams[constants.ND_OOB_PROGRAM] is None): + # will be reset by the line below + del ndparams[constants.ND_OOB_PROGRAM] + return FillDict(constants.NDC_DEFAULTS, ndparams) + + +def MakeEmptyIPolicy(): + """Create empty IPolicy dictionary. + + """ + return dict([ + (constants.ISPECS_MIN, {}), + (constants.ISPECS_MAX, {}), + (constants.ISPECS_STD, {}), + ]) + + +class ConfigObject(objectutils.ValidatedSlots): """A generic config object. It has the following properties: @@ -151,31 +207,22 @@ class ConfigObject(object): """ __slots__ = [] - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - def __getattr__(self, name): - if name not in self._all_slots(): + if name not in self.GetAllSlots(): raise AttributeError("Invalid object attribute %s.%s" % (type(self).__name__, name)) return None def __setstate__(self, state): - slots = self._all_slots() + slots = self.GetAllSlots() for name in state: if name in slots: setattr(self, name, state[name]) - @classmethod - def _all_slots(cls): - """Compute the list of all declared slots for a class. + def Validate(self): + """Validates the slots. """ - 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. @@ -188,7 +235,7 @@ class ConfigObject(object): """ result = {} - for name in self._all_slots(): + for name in self.GetAllSlots(): value = getattr(self, name, None) if value is not None: result[name] = value @@ -373,7 +420,7 @@ class MasterNetworkParameters(ConfigObject): "ip", "netmask", "netdev", - "ip_family" + "ip_family", ] @@ -385,6 +432,7 @@ class ConfigData(ConfigObject): "nodes", "nodegroups", "instances", + "networks", "serial_no", ] + _TIMESTAMPS @@ -397,7 +445,7 @@ class ConfigData(ConfigObject): """ mydict = super(ConfigData, self).ToDict() mydict["cluster"] = mydict["cluster"].ToDict() - for key in "nodes", "instances", "nodegroups": + for key in "nodes", "instances", "nodegroups", "networks": mydict[key] = self._ContainerToDicts(mydict[key]) return mydict @@ -412,6 +460,7 @@ class ConfigData(ConfigObject): obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node) obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance) obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup) + obj.networks = cls._ContainerFromDicts(obj.networks, dict, Network) return obj def HasAnyDiskOfType(self, dev_type): @@ -448,11 +497,13 @@ class ConfigData(ConfigObject): # gives a good approximation. if self.HasAnyDiskOfType(constants.LD_DRBD8): self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER + if self.networks is None: + self.networks = {} class NIC(ConfigObject): """Config object representing a network card.""" - __slots__ = ["mac", "ip", "nicparams"] + __slots__ = ["mac", "ip", "network", "nicparams", "netinfo"] @classmethod def CheckParameterSyntax(cls, nicparams): @@ -506,6 +557,8 @@ class Disk(ConfigObject): return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) elif self.dev_type == constants.LD_BLOCKDEV: return self.logical_id[1] + elif self.dev_type == constants.LD_RBD: + return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) return None def ChildrenNeeded(self): @@ -549,7 +602,7 @@ class Disk(ConfigObject): """ if self.dev_type in [constants.LD_LV, constants.LD_FILE, - constants.LD_BLOCKDEV]: + constants.LD_BLOCKDEV, constants.LD_RBD]: result = [node] elif self.dev_type in constants.LDS_DRBD: result = [self.logical_id[0], self.logical_id[1]] @@ -624,7 +677,8 @@ class Disk(ConfigObject): actual algorithms from bdev. """ - if self.dev_type in (constants.LD_LV, constants.LD_FILE): + if self.dev_type in (constants.LD_LV, constants.LD_FILE, + constants.LD_RBD): self.size += amount elif self.dev_type == constants.LD_DRBD8: if self.children: @@ -634,6 +688,21 @@ class Disk(ConfigObject): raise errors.ProgrammerError("Disk.RecordGrow called for unsupported" " disk type %s" % self.dev_type) + def Update(self, size=None, mode=None): + """Apply changes to size and mode. + + """ + if self.dev_type == constants.LD_DRBD8: + if self.children: + self.children[0].Update(size=size, mode=mode) + else: + assert not self.children + + if size is not None: + self.size = size + if mode is not None: + self.mode = mode + def UnsetSize(self): """Sets recursively the size to zero for the disk and its children. @@ -772,13 +841,158 @@ class Disk(ConfigObject): for child in self.children: child.UpgradeConfig() - if not self.params: - self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy() - else: - self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type], - self.params) + # FIXME: Make this configurable in Ganeti 2.7 + self.params = {} # add here config upgrade for this disk + @staticmethod + def ComputeLDParams(disk_template, disk_params): + """Computes Logical Disk parameters from Disk Template parameters. + + @type disk_template: string + @param disk_template: disk template, one of L{constants.DISK_TEMPLATES} + @type disk_params: dict + @param disk_params: disk template parameters; + dict(template_name -> parameters + @rtype: list(dict) + @return: a list of dicts, one for each node of the disk hierarchy. Each dict + contains the LD parameters of the node. The tree is flattened in-order. + + """ + if disk_template not in constants.DISK_TEMPLATES: + raise errors.ProgrammerError("Unknown disk template %s" % disk_template) + + assert disk_template in disk_params + + result = list() + dt_params = disk_params[disk_template] + if disk_template == constants.DT_DRBD8: + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8], { + constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE], + constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS], + constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS], + constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG], + constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM], + constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM], + constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC], + constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD], + constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET], + constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET], + constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE], + constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE], + })) + + # data LV + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { + constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES], + })) + + # metadata LV + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { + constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES], + })) + + elif disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE): + result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE]) + + elif disk_template == constants.DT_PLAIN: + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { + constants.LDP_STRIPES: dt_params[constants.LV_STRIPES], + })) + + elif disk_template == constants.DT_BLOCK: + result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV]) + + elif disk_template == constants.DT_RBD: + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], { + constants.LDP_POOL: dt_params[constants.RBD_POOL], + })) + + return result + + +class InstancePolicy(ConfigObject): + """Config object representing instance policy limits dictionary. + + + Note that this object is not actually used in the config, it's just + used as a placeholder for a few functions. + + """ + @classmethod + def CheckParameterSyntax(cls, ipolicy, check_std): + """ Check the instance policy for validity. + + """ + for param in constants.ISPECS_PARAMETERS: + InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std) + if constants.IPOLICY_DTS in ipolicy: + InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS]) + for key in constants.IPOLICY_PARAMETERS: + if key in ipolicy: + InstancePolicy.CheckParameter(key, ipolicy[key]) + wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS + if wrong_keys: + raise errors.ConfigurationError("Invalid keys in ipolicy: %s" % + utils.CommaJoin(wrong_keys)) + + @classmethod + def CheckISpecSyntax(cls, ipolicy, name, check_std): + """Check the instance policy for validity on a given key. + + We check if the instance policy makes sense for a given key, that is + if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name]. + + @type ipolicy: dict + @param ipolicy: dictionary with min, max, std specs + @type name: string + @param name: what are the limits for + @type check_std: bool + @param check_std: Whether to check std value or just assume compliance + @raise errors.ConfigureError: when specs for given name are not valid + + """ + min_v = ipolicy[constants.ISPECS_MIN].get(name, 0) + + if check_std: + std_v = ipolicy[constants.ISPECS_STD].get(name, min_v) + std_msg = std_v + else: + std_v = min_v + std_msg = "-" + + max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v) + err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" % + (name, + ipolicy[constants.ISPECS_MIN].get(name, "-"), + ipolicy[constants.ISPECS_MAX].get(name, "-"), + std_msg)) + if min_v > std_v or std_v > max_v: + raise errors.ConfigurationError(err) + + @classmethod + def CheckDiskTemplates(cls, disk_templates): + """Checks the disk templates for validity. + + """ + wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES) + if wrong: + raise errors.ConfigurationError("Invalid disk template(s) %s" % + utils.CommaJoin(wrong)) + + @classmethod + def CheckParameter(cls, key, value): + """Checks a parameter. + + Currently we expect all parameters to be float values. + + """ + try: + float(value) + except (TypeError, ValueError), err: + raise errors.ConfigurationError("Invalid value for key" " '%s':" + " '%s', error: %s" % (key, value, err)) + class Instance(TaggableObject): """Config object representing an instance.""" @@ -854,7 +1068,7 @@ class Instance(TaggableObject): GetVolumeList() """ - if node == None: + if node is None: node = self.primary_node if lvmap is None: @@ -1144,8 +1358,12 @@ class NodeGroup(TaggableObject): "members", "ndparams", "diskparams", + "ipolicy", "serial_no", + "hv_state_static", + "disk_state_static", "alloc_policy", + "networks", ] + _TIMESTAMPS + _UUID def ToDict(self): @@ -1183,12 +1401,18 @@ class NodeGroup(TaggableObject): 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. + # 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() - self.diskparams = UpgradeDiskParams(self.diskparams) + if self.diskparams is None: + self.diskparams = {} + if self.ipolicy is None: + self.ipolicy = MakeEmptyIPolicy() + + if self.networks is None: + self.networks = {} def FillND(self, node): """Return filled out ndparams for L{objects.Node} @@ -1236,6 +1460,7 @@ class Cluster(TaggableObject): "shared_file_storage_dir", "enabled_hypervisors", "hvparams", + "ipolicy", "os_hvp", "beparams", "osparams", @@ -1252,6 +1477,8 @@ class Cluster(TaggableObject): "blacklisted_os", "primary_ip_family", "prealloc_wipe_disks", + "hv_state_static", + "disk_state_static", ] + _TIMESTAMPS + _UUID def UpgradeConfig(self): @@ -1274,8 +1501,7 @@ class Cluster(TaggableObject): if self.osparams is None: self.osparams = {} - if self.ndparams is None: - self.ndparams = constants.NDC_DEFAULTS + self.ndparams = UpgradeNDParams(self.ndparams) self.beparams = UpgradeGroupedParams(self.beparams, constants.BEC_DEFAULTS) @@ -1305,8 +1531,8 @@ class Cluster(TaggableObject): # 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] + - [hvname for hvname in self.enabled_hypervisors - if hvname != self.default_hypervisor]) + [hvname for hvname in self.enabled_hypervisors + if hvname != self.default_hypervisor]) self.default_hypervisor = None # maintain_node_health added after 2.1.1 @@ -1348,7 +1574,19 @@ class Cluster(TaggableObject): if self.use_external_mip_script is None: self.use_external_mip_script = False - self.diskparams = UpgradeDiskParams(self.diskparams) + if self.diskparams: + self.diskparams = UpgradeDiskParams(self.diskparams) + else: + self.diskparams = constants.DISK_DT_DEFAULTS.copy() + + # instance policy added before 2.6 + if self.ipolicy is None: + self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {}) + else: + # we can either make sure to upgrade the ipolicy always, or only + # do it in some corner cases (e.g. missing keys); note that this + # will break any removal of keys from the ipolicy dict + self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy) @property def primary_hypervisor(self): @@ -1377,6 +1615,15 @@ class Cluster(TaggableObject): obj.tcpudp_port_pool = set(obj.tcpudp_port_pool) return obj + def SimpleFillDP(self, diskparams): + """Fill a given diskparams dict with cluster defaults. + + @param diskparams: The diskparams + @return: The defaults dict + + """ + return FillDiskParams(self.diskparams, diskparams) + def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None): """Get the default hypervisor parameters for the cluster. @@ -1495,6 +1742,20 @@ class Cluster(TaggableObject): # specified params return FillDict(result, os_params) + @staticmethod + def SimpleFillHvState(hv_state): + """Fill an hv_state sub dict with cluster defaults. + + """ + return FillDict(constants.HVST_DEFAULTS, hv_state) + + @staticmethod + def SimpleFillDiskState(disk_state): + """Fill an disk_state sub dict with cluster defaults. + + """ + return FillDict(constants.DS_DEFAULTS, disk_state) + def FillND(self, node, nodegroup): """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node} @@ -1519,6 +1780,18 @@ class Cluster(TaggableObject): """ return FillDict(self.ndparams, ndparams) + def SimpleFillIPolicy(self, ipolicy): + """ Fill instance policy dict with defaults. + + @type ipolicy: dict + @param ipolicy: the dict to fill + @rtype: dict + @return: a copy of passed ipolicy with missing keys filled from + the cluster defaults + + """ + return FillIPolicy(self.ipolicy, ipolicy) + class BlockDevStatus(ConfigObject): """Config object representing the status of a block device.""" @@ -1643,17 +1916,6 @@ class _QueryResponseBase(ConfigObject): return obj -class QueryRequest(ConfigObject): - """Object holding a query request. - - """ - __slots__ = [ - "what", - "fields", - "qfilter", - ] - - class QueryResponse(_QueryResponseBase): """Object holding the response to a query. @@ -1682,8 +1944,7 @@ class QueryFieldsResponse(_QueryResponseBase): @ivar fields: List of L{QueryFieldDefinition} objects """ - __slots__ = [ - ] + __slots__ = [] class MigrationStatus(ConfigObject): @@ -1736,6 +1997,26 @@ class InstanceConsole(ConfigObject): return True +class Network(TaggableObject): + """Object representing a network definition for ganeti. + + """ + __slots__ = [ + "name", + "serial_no", + "network_type", + "mac_prefix", + "family", + "network", + "network6", + "gateway", + "gateway6", + "size", + "reservations", + "ext_reservations", + ] + _TIMESTAMPS + _UUID + + class SerializableConfigParser(ConfigParser.SafeConfigParser): """Simple wrapper over ConfigParse that allows serialization.