X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/634d30f4694d374e2d84386434cf33967e2d57fd..12378fe30a39ae0f99680bce84963dea59e4665e:/lib/objects.py diff --git a/lib/objects.py b/lib/objects.py index fc077b3..96c5440 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 @@ -44,6 +44,7 @@ from cStringIO import StringIO from ganeti import errors from ganeti import constants from ganeti import netutils +from ganeti import utils from socket import AF_INET @@ -54,6 +55,18 @@ __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", _TIMESTAMPS = ["ctime", "mtime"] _UUID = ["uuid"] +# constants used to create InstancePolicy dictionary +TISPECS_GROUP_TYPES = { + constants.ISPECS_MIN: constants.VTYPE_INT, + constants.ISPECS_MAX: constants.VTYPE_INT, + } + +TISPECS_CLUSTER_TYPES = { + constants.ISPECS_MIN: constants.VTYPE_INT, + constants.ISPECS_MAX: constants.VTYPE_INT, + constants.ISPECS_STD: constants.VTYPE_INT, + } + def FillDict(defaults_dict, custom_dict, skip_keys=None): """Basic function to apply settings on top a default dict. @@ -79,6 +92,23 @@ 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])) + + return ret_dict + + def UpgradeGroupedParams(target, defaults): """Update all groups for the target parameter. @@ -110,6 +140,95 @@ def UpgradeBeParams(target): del target[constants.BE_MEMORY] +def UpgradeDiskParams(diskparams): + """Upgrade the disk parameters. + + @type diskparams: dict + @param diskparams: disk parameters to upgrade + @rtype: dict + @return: the upgraded disk parameters dit + + """ + result = dict() + if diskparams is None: + result = constants.DISK_DT_DEFAULTS.copy() + 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]) + + return result + + +def MakeEmptyIPolicy(): + """Create empty IPolicy dictionary. + + """ + return dict([ + (constants.ISPECS_MIN, {}), + (constants.ISPECS_MAX, {}), + (constants.ISPECS_STD, {}), + ]) + + +def CreateIPolicyFromOpts(ispecs_mem_size=None, + ispecs_cpu_count=None, + ispecs_disk_count=None, + ispecs_disk_size=None, + ispecs_nic_count=None, + ipolicy_disk_templates=None, + group_ipolicy=False, + allowed_values=None, + fill_all=False): + """Creation of instance policy based on command line options. + + @param fill_all: whether for cluster policies we should ensure that + all values are filled + + + """ + # prepare ipolicy dict + ipolicy_transposed = { + constants.ISPEC_MEM_SIZE: ispecs_mem_size, + constants.ISPEC_CPU_COUNT: ispecs_cpu_count, + constants.ISPEC_DISK_COUNT: ispecs_disk_count, + constants.ISPEC_DISK_SIZE: ispecs_disk_size, + constants.ISPEC_NIC_COUNT: ispecs_nic_count, + } + + # first, check that the values given are correct + if group_ipolicy: + forced_type = TISPECS_GROUP_TYPES + else: + forced_type = TISPECS_CLUSTER_TYPES + + for specs in ipolicy_transposed.values(): + utils.ForceDictType(specs, forced_type, allowed_values=allowed_values) + + # then transpose + ipolicy_out = MakeEmptyIPolicy() + for name, specs in ipolicy_transposed.iteritems(): + assert name in constants.ISPECS_PARAMETERS + for key, val in specs.items(): # {min: .. ,max: .., std: ..} + ipolicy_out[key][name] = val + + # no filldict for lists + if not group_ipolicy and fill_all and ipolicy_disk_templates is None: + ipolicy_disk_templates = constants.DISK_TEMPLATES + if ipolicy_disk_templates is not None: + ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates) + + assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS) + + return ipolicy_out + + class ConfigObject(object): """A generic config object. @@ -451,7 +570,7 @@ class NIC(ConfigObject): class Disk(ConfigObject): """Config object representing a block device.""" __slots__ = ["dev_type", "logical_id", "physical_id", - "children", "iv_name", "size", "mode"] + "children", "iv_name", "size", "mode", "params"] def CreateOnSecondary(self): """Test if this device needs to be created on a secondary node.""" @@ -480,6 +599,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): @@ -523,7 +644,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]] @@ -598,7 +719,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: @@ -608,6 +730,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. @@ -745,9 +882,69 @@ class Disk(ConfigObject): if self.children: 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) # add here config upgrade for this disk +class InstancePolicy(ConfigObject): + """Config object representing instance policy limits dictionary.""" + __slots__ = ["min", "max", "std", "disk_templates"] + + @classmethod + def CheckParameterSyntax(cls, ipolicy): + """ Check the instance policy for validity. + + """ + for param in constants.ISPECS_PARAMETERS: + InstancePolicy.CheckISpecSyntax(ipolicy, param) + if constants.IPOLICY_DTS in ipolicy: + InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS]) + 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 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 + @raise errors.ConfigureError: when specs for given name are not valid + + """ + min_v = ipolicy[constants.ISPECS_MIN].get(name, 0) + std_v = ipolicy[constants.ISPECS_STD].get(name, min_v) + 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, "-"), + ipolicy[constants.ISPECS_STD].get(name, "-"))) + 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)) + + class Instance(TaggableObject): """Config object representing an instance.""" __slots__ = [ @@ -989,6 +1186,40 @@ class OS(ConfigObject): return cls.SplitNameVariant(name)[1] +class NodeHvState(ConfigObject): + """Hypvervisor state on a node. + + @ivar mem_total: Total amount of memory + @ivar mem_node: Memory used by, or reserved for, the node itself (not always + available) + @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation + rounding + @ivar mem_inst: Memory used by instances living on node + @ivar cpu_total: Total node CPU core count + @ivar cpu_node: Number of CPU cores reserved for the node itself + + """ + __slots__ = [ + "mem_total", + "mem_node", + "mem_hv", + "mem_inst", + "cpu_total", + "cpu_node", + ] + _TIMESTAMPS + + +class NodeDiskState(ConfigObject): + """Disk state on a node. + + """ + __slots__ = [ + "total", + "reserved", + "overhead", + ] + _TIMESTAMPS + + class Node(TaggableObject): """Config object representing a node. @@ -1035,6 +1266,41 @@ class Node(TaggableObject): if self.powered is None: self.powered = True + def ToDict(self): + """Custom function for serializing. + + """ + data = super(Node, self).ToDict() + + hv_state = data.get("hv_state", None) + if hv_state is not None: + data["hv_state"] = self._ContainerToDicts(hv_state) + + disk_state = data.get("disk_state", None) + if disk_state is not None: + data["disk_state"] = \ + dict((key, self._ContainerToDicts(value)) + for (key, value) in disk_state.items()) + + return data + + @classmethod + def FromDict(cls, val): + """Custom function for deserializing. + + """ + obj = super(Node, cls).FromDict(val) + + if obj.hv_state is not None: + obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState) + + if obj.disk_state is not None: + obj.disk_state = \ + dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState)) + for (key, value) in obj.disk_state.items()) + + return obj + class NodeGroup(TaggableObject): """Config object representing a node group.""" @@ -1042,7 +1308,11 @@ class NodeGroup(TaggableObject): "name", "members", "ndparams", + "diskparams", + "ipolicy", "serial_no", + "hv_state_static", + "disk_state_static", "alloc_policy", ] + _TIMESTAMPS + _UUID @@ -1081,11 +1351,15 @@ 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.ipolicy is None: + self.ipolicy = MakeEmptyIPolicy() + def FillND(self, node): """Return filled out ndparams for L{objects.Node} @@ -1132,11 +1406,13 @@ class Cluster(TaggableObject): "shared_file_storage_dir", "enabled_hypervisors", "hvparams", + "ipolicy", "os_hvp", "beparams", "osparams", "nicparams", "ndparams", + "diskparams", "candidate_pool_size", "modify_etc_hosts", "modify_ssh_setup", @@ -1147,6 +1423,8 @@ class Cluster(TaggableObject): "blacklisted_os", "primary_ip_family", "prealloc_wipe_disks", + "hv_state_static", + "disk_state_static", ] + _TIMESTAMPS + _UUID def UpgradeConfig(self): @@ -1243,6 +1521,26 @@ class Cluster(TaggableObject): if self.use_external_mip_script is None: self.use_external_mip_script = False + self.diskparams = UpgradeDiskParams(self.diskparams) + + # 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): + """The first hypervisor is the primary. + + Useful, for example, for L{Node}'s hv/disk state. + + """ + return self.enabled_hypervisors[0] + def ToDict(self): """Custom function for cluster. @@ -1379,6 +1677,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} @@ -1403,6 +1715,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."""