#
#
-# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
"""
-# pylint: disable-msg=E0203,W0201
+# 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
# W0201: Attribute '%s' defined outside __init__
+# R0902: Allow instances of these objects to have more than 20 attributes
+
import ConfigParser
import re
import copy
from ganeti import errors
from ganeti import constants
+from ganeti import netutils
+from ganeti import utils
from socket import AF_INET
_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.
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.
return target
+def UpgradeBeParams(target):
+ """Update the be parameters dict to the new format.
+
+ @type target: dict
+ @param target: "be" parameters dict
+
+ """
+ if constants.BE_MEMORY in target:
+ memory = target[constants.BE_MEMORY]
+ target[constants.BE_MAXMEM] = memory
+ target[constants.BE_MINMEM] = memory
+ 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.
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) # pylint: disable-msg=W0142
+ obj = cls(**val_str) # pylint: disable=W0142
return obj
@staticmethod
return obj
+class MasterNetworkParameters(ConfigObject):
+ """Network configuration parameters for the master
+
+ @ivar name: master name
+ @ivar ip: master IP
+ @ivar netmask: master netmask
+ @ivar netdev: master network device
+ @ivar ip_family: master IP family
+
+ """
+ __slots__ = [
+ "name",
+ "ip",
+ "netmask",
+ "netdev",
+ "ip_family"
+ ]
+
+
class ConfigData(ConfigObject):
"""Top-level config object."""
__slots__ = [
@raise errors.ConfigurationError: when a parameter is not valid
"""
- if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
+ 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)
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."""
"""
if self.dev_type == constants.LD_LV:
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):
devices needs to (or can) be assembled.
"""
- if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
+ if self.dev_type in [constants.LD_LV, constants.LD_FILE,
+ 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]]
# be different)
return result
+ def ComputeGrowth(self, amount):
+ """Compute the per-VG growth requirements.
+
+ This only works for VG-based disks.
+
+ @type amount: integer
+ @param amount: the desired increase in (user-visible) disk space
+ @rtype: dict
+ @return: a dictionary of volume-groups and the required size
+
+ """
+ if self.dev_type == constants.LD_LV:
+ return {self.logical_id[0]: amount}
+ elif self.dev_type == constants.LD_DRBD8:
+ if self.children:
+ return self.children[0].ComputeGrowth(amount)
+ else:
+ return {}
+ else:
+ # Other disk types do not require VG space
+ return {}
+
def RecordGrow(self, amount):
"""Update the size of this disk after growth.
actual algorithms from bdev.
"""
- if self.dev_type == constants.LD_LV or self.dev_type == 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:
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.
"""
if self.dev_type == constants.LD_LV:
- val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
+ val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
elif self.dev_type in constants.LDS_DRBD:
node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
val = "<DRBD8("
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__ = [
"hvparams",
"beparams",
"osparams",
- "admin_up",
+ "admin_state",
"nics",
"disks",
"disk_template",
node = self.primary_node
if lvmap is None:
- lvmap = { node : [] }
+ lvmap = {
+ node: [],
+ }
ret = lvmap
else:
if not node in lvmap:
for dev in devs:
if dev.dev_type == constants.LD_LV:
- lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
+ lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
elif dev.dev_type in constants.LDS_DRBD:
if dev.children:
errors.ECODE_INVAL)
except IndexError:
raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
- " 0 to %d" % (idx, len(self.disks)),
+ " 0 to %d" % (idx, len(self.disks) - 1),
errors.ECODE_INVAL)
def ToDict(self):
"""Custom function for instances.
"""
+ if "admin_state" not in val:
+ if val.get("admin_up", False):
+ val["admin_state"] = constants.ADMINST_UP
+ else:
+ val["admin_state"] = constants.ADMINST_DOWN
+ if "admin_up" in val:
+ del val["admin_up"]
obj = super(Instance, cls).FromDict(val)
obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
pass
if self.osparams is None:
self.osparams = {}
+ UpgradeBeParams(self.beparams)
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."""
+ """Config object representing a node.
+
+ @ivar hv_state: Hypervisor state (e.g. number of CPUs)
+ @ivar hv_state_static: Hypervisor state overriden by user
+ @ivar disk_state: Disk state (e.g. free space)
+ @ivar disk_state_static: Disk state overriden by user
+
+ """
__slots__ = [
"name",
"primary_ip",
"vm_capable",
"ndparams",
"powered",
+ "hv_state",
+ "hv_state_static",
+ "disk_state",
+ "disk_state_static",
] + _TIMESTAMPS + _UUID
def UpgradeConfig(self):
"""Fill defaults for missing configuration values.
"""
- # pylint: disable-msg=E0203
+ # pylint: disable=E0203
# because these are "defined" via slots, not manually
if self.master_capable is None:
self.master_capable = True
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(ConfigObject):
+class NodeGroup(TaggableObject):
"""Config object representing a node group."""
__slots__ = [
"name",
"members",
"ndparams",
+ "diskparams",
+ "ipolicy",
"serial_no",
+ "hv_state_static",
+ "disk_state_static",
"alloc_policy",
] + _TIMESTAMPS + _UUID
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{object.Node}
+ """Return filled out ndparams for L{objects.Node}
@type node: L{objects.Node}
@param node: A Node object to fill
"master_node",
"master_ip",
"master_netdev",
+ "master_netmask",
+ "use_external_mip_script",
"cluster_name",
"file_storage_dir",
+ "shared_file_storage_dir",
"enabled_hypervisors",
"hvparams",
+ "ipolicy",
"os_hvp",
"beparams",
"osparams",
"nicparams",
"ndparams",
+ "diskparams",
"candidate_pool_size",
"modify_etc_hosts",
"modify_ssh_setup",
"blacklisted_os",
"primary_ip_family",
"prealloc_wipe_disks",
+ "hv_state_static",
+ "disk_state_static",
] + _TIMESTAMPS + _UUID
def UpgradeConfig(self):
"""Fill defaults for missing configuration values.
"""
- # pylint: disable-msg=E0203
+ # pylint: disable=E0203
# because these are "defined" via slots, not manually
if self.hvparams is None:
self.hvparams = constants.HVC_DEFAULTS
self.beparams = UpgradeGroupedParams(self.beparams,
constants.BEC_DEFAULTS)
+ for beparams_group in self.beparams:
+ UpgradeBeParams(self.beparams[beparams_group])
+
migrate_default_bridge = not self.nicparams
self.nicparams = UpgradeGroupedParams(self.nicparams,
constants.NICC_DEFAULTS)
if self.modify_ssh_setup is None:
self.modify_ssh_setup = True
- # default_bridge is no longer used it 2.1. The slot is left there to
+ # default_bridge is no longer used in 2.1. The slot is left there to
# support auto-upgrading. It can be removed once we decide to deprecate
# upgrading straight from 2.0.
if self.default_bridge is not None:
if self.primary_ip_family is None:
self.primary_ip_family = AF_INET
+ if self.master_netmask is None:
+ ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
+ self.master_netmask = ipcls.iplen
+
if self.prealloc_wipe_disks is None:
self.prealloc_wipe_disks = False
+ # shared_file_storage_dir added before 2.5
+ if self.shared_file_storage_dir is None:
+ self.shared_file_storage_dir = ""
+
+ 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.
# 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{object.Node}
+ """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
@type node: L{objects.Node}
@param node: A Node object to fill
"""
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."""
@ivar compress: Compression method (one of L{constants.IEC_ALL})
@ivar magic: Used to ensure the connection goes to the right disk
@ivar ipv6: Whether to use IPv6
+ @ivar connect_timeout: Number of seconds for establishing connection
"""
__slots__ = [
"compress",
"magic",
"ipv6",
+ "connect_timeout",
]
@ivar name: Field name
@ivar title: Human-readable title
@ivar kind: Field type
+ @ivar doc: Human-readable description
"""
__slots__ = [
"name",
"title",
"kind",
+ "doc",
]
__slots__ = [
"what",
"fields",
- "filter",
+ "qfilter",
]
]
+class MigrationStatus(ConfigObject):
+ """Object holding the status of a migration.
+
+ """
+ __slots__ = [
+ "status",
+ "transferred_ram",
+ "total_ram",
+ ]
+
+
class InstanceConsole(ConfigObject):
"""Object describing how to access the console of an instance.
"""
assert self.kind in constants.CONS_ALL, "Unknown console type"
assert self.instance, "Missing instance name"
- assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
+ assert self.message or self.kind in [constants.CONS_SSH,
+ constants.CONS_SPICE,
+ constants.CONS_VNC]
assert self.host or self.kind == constants.CONS_MESSAGE
assert self.port or self.kind in [constants.CONS_MESSAGE,
constants.CONS_SSH]
assert self.user or self.kind in [constants.CONS_MESSAGE,
+ constants.CONS_SPICE,
constants.CONS_VNC]
assert self.command or self.kind in [constants.CONS_MESSAGE,
+ constants.CONS_SPICE,
constants.CONS_VNC]
assert self.display or self.kind in [constants.CONS_MESSAGE,
+ constants.CONS_SPICE,
constants.CONS_SSH]
return True