X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/cd46491ffc9b2e708a58799332cd0bad4836f88d..b76d4aaf4b54b049658cbf3dbd762431ecfcc51d:/lib/objects.py diff --git a/lib/objects.py b/lib/objects.py index 24b99fc..f8667e3 100644 --- a/lib/objects.py +++ b/lib/objects.py @@ -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,29 +44,18 @@ 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"] -# 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. @@ -115,7 +104,7 @@ def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None): def FillDiskParams(default_dparams, custom_dparams, skip_keys=None): """Fills the disk parameter defaults. - @see FillDict: For parameters and return value + @see: L{FillDict} for parameters and return value """ assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES @@ -165,8 +154,8 @@ def UpgradeDiskParams(diskparams): @return: the upgraded disk parameters dict """ - if diskparams is None: - result = constants.DISK_DT_DEFAULTS.copy() + if not diskparams: + result = {} else: result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams) @@ -185,6 +174,10 @@ def UpgradeNDParams(ndparams): 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) @@ -199,66 +192,7 @@ def MakeEmptyIPolicy(): ]) -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, - ipolicy_vcpu_ratio=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 non-dicts - if not group_ipolicy and fill_all: - if ipolicy_disk_templates is None: - ipolicy_disk_templates = constants.DISK_TEMPLATES - if ipolicy_vcpu_ratio is None: - ipolicy_vcpu_ratio = \ - constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO] - if ipolicy_disk_templates is not None: - ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates) - if ipolicy_vcpu_ratio is not None: - ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio - - assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS) - - return ipolicy_out - - -class ConfigObject(object): +class ConfigObject(objectutils.ValidatedSlots): """A generic config object. It has the following properties: @@ -273,34 +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 - - #: Public getter for the defined slots - GetAllSlots = _all_slots def ToDict(self): """Convert to a dict holding only standard python types. @@ -313,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 @@ -498,7 +420,7 @@ class MasterNetworkParameters(ConfigObject): "ip", "netmask", "netdev", - "ip_family" + "ip_family", ] @@ -510,6 +432,7 @@ class ConfigData(ConfigObject): "nodes", "nodegroups", "instances", + "networks", "serial_no", ] + _TIMESTAMPS @@ -522,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 @@ -537,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): @@ -573,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): @@ -588,15 +514,14 @@ class NIC(ConfigObject): @raise errors.ConfigurationError: when a parameter is not valid """ - if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and - nicparams[constants.NIC_MODE] != constants.VALUE_AUTO): - err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE] - raise errors.ConfigurationError(err) + mode = nicparams[constants.NIC_MODE] + if (mode not in constants.NIC_VALID_MODES and + mode != constants.VALUE_AUTO): + raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode) - if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and + if (mode == constants.NIC_MODE_BRIDGED and not nicparams[constants.NIC_LINK]): - err = "Missing bridged nic link" - raise errors.ConfigurationError(err) + raise errors.ConfigurationError("Missing bridged NIC link") class Disk(ConfigObject): @@ -676,7 +601,8 @@ class Disk(ConfigObject): """ if self.dev_type in [constants.LD_LV, constants.LD_FILE, - constants.LD_BLOCKDEV, constants.LD_RBD]: + constants.LD_BLOCKDEV, constants.LD_RBD, + constants.LD_EXT]: result = [node] elif self.dev_type in constants.LDS_DRBD: result = [self.logical_id[0], self.logical_id[1]] @@ -752,7 +678,7 @@ class Disk(ConfigObject): """ if self.dev_type in (constants.LD_LV, constants.LD_FILE, - constants.LD_RBD): + constants.LD_RBD, constants.LD_EXT): self.size += amount elif self.dev_type == constants.LD_DRBD8: if self.children: @@ -915,11 +841,8 @@ 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 @@ -944,7 +867,7 @@ class Disk(ConfigObject): result = list() dt_params = disk_params[disk_template] if disk_template == constants.DT_DRBD8: - drbd_params = { + 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], @@ -957,56 +880,36 @@ class Disk(ConfigObject): 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], - } - - drbd_params = \ - FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8], - drbd_params) - - result.append(drbd_params) + })) # data LV - data_params = { + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES], - } - data_params = \ - FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], - data_params) - result.append(data_params) + })) # metadata LV - meta_params = { + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES], - } - meta_params = \ - FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], - meta_params) - result.append(meta_params) + })) - elif (disk_template == constants.DT_FILE or - disk_template == constants.DT_SHARED_FILE): + 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: - params = { + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], { constants.LDP_STRIPES: dt_params[constants.LV_STRIPES], - } - params = \ - FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV], - params) - result.append(params) + })) elif disk_template == constants.DT_BLOCK: result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV]) elif disk_template == constants.DT_RBD: - params = { - constants.LDP_POOL: dt_params[constants.RBD_POOL] - } - params = \ - FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], - params) - result.append(params) + result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], { + constants.LDP_POOL: dt_params[constants.RBD_POOL], + })) + + elif disk_template == constants.DT_EXT: + result.append(constants.DISK_LD_DEFAULTS[constants.LD_EXT]) return result @@ -1020,12 +923,12 @@ class InstancePolicy(ConfigObject): """ @classmethod - def CheckParameterSyntax(cls, ipolicy): + def CheckParameterSyntax(cls, ipolicy, check_std): """ Check the instance policy for validity. """ for param in constants.ISPECS_PARAMETERS: - InstancePolicy.CheckISpecSyntax(ipolicy, param) + InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std) if constants.IPOLICY_DTS in ipolicy: InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS]) for key in constants.IPOLICY_PARAMETERS: @@ -1037,7 +940,7 @@ class InstancePolicy(ConfigObject): utils.CommaJoin(wrong_keys)) @classmethod - def CheckISpecSyntax(cls, ipolicy, name): + 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 @@ -1047,17 +950,26 @@ class InstancePolicy(ConfigObject): @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) - std_v = ipolicy[constants.ISPECS_STD].get(name, min_v) + + 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, "-"), - ipolicy[constants.ISPECS_STD].get(name, "-"))) + std_msg)) if min_v > std_v or std_v > max_v: raise errors.ConfigurationError(err) @@ -1114,7 +1026,7 @@ class Instance(TaggableObject): return tuple(all_nodes) secondary_nodes = property(_ComputeSecondaryNodes, None, None, - "List of secondary nodes") + "List of names of secondary nodes") def _ComputeAllNodes(self): """Compute the list of all nodes. @@ -1142,7 +1054,7 @@ class Instance(TaggableObject): return tuple(all_nodes) all_nodes = property(_ComputeAllNodes, None, None, - "List of all nodes of the instance") + "List of names of all the nodes of the instance") def MapLVsByNode(self, lvmap=None, devs=None, node=None): """Provide a mapping of nodes to LVs this instance owns. @@ -1159,7 +1071,7 @@ class Instance(TaggableObject): GetVolumeList() """ - if node == None: + if node is None: node = self.primary_node if lvmap is None: @@ -1326,6 +1238,24 @@ class OS(ConfigObject): return cls.SplitNameVariant(name)[1] +class ExtStorage(ConfigObject): + """Config object representing an External Storage Provider. + + """ + __slots__ = [ + "name", + "path", + "create_script", + "remove_script", + "grow_script", + "attach_script", + "detach_script", + "setinfo_script", + "verify_script", + "supported_parameters", + ] + + class NodeHvState(ConfigObject): """Hypvervisor state on a node. @@ -1454,6 +1384,7 @@ class NodeGroup(TaggableObject): "hv_state_static", "disk_state_static", "alloc_policy", + "networks", ] + _TIMESTAMPS + _UUID def ToDict(self): @@ -1496,10 +1427,14 @@ class NodeGroup(TaggableObject): 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} @@ -1617,8 +1552,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 @@ -1660,7 +1595,10 @@ 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: @@ -2027,8 +1965,7 @@ class QueryFieldsResponse(_QueryResponseBase): @ivar fields: List of L{QueryFieldDefinition} objects """ - __slots__ = [ - ] + __slots__ = [] class MigrationStatus(ConfigObject): @@ -2081,6 +2018,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. @@ -2102,3 +2059,41 @@ class SerializableConfigParser(ConfigParser.SafeConfigParser): cfp = cls() cfp.readfp(buf) return cfp + + +class LvmPvInfo(ConfigObject): + """Information about an LVM physical volume (PV). + + @type name: string + @ivar name: name of the PV + @type vg_name: string + @ivar vg_name: name of the volume group containing the PV + @type size: float + @ivar size: size of the PV in MiB + @type free: float + @ivar free: free space in the PV, in MiB + @type attributes: string + @ivar attributes: PV attributes + @type lv_list: list of strings + @ivar lv_list: names of the LVs hosted on the PV + """ + __slots__ = [ + "name", + "vg_name", + "size", + "free", + "attributes", + "lv_list" + ] + + def IsEmpty(self): + """Is this PV empty? + + """ + return self.size <= (self.free + 1) + + def IsAllocatable(self): + """Is this PV allocatable? + + """ + return ("a" in self.attributes)