#
#
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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=R0904
# R0904: Too many public methods
+import copy
import os
import random
import logging
return all_reserved
def GetECReserved(self, ec_id):
+ """ Used when you want to retrieve all reservations for a specific
+ execution context. E.g when commiting reserved IPs for a specific
+ network.
+
+ """
ec_reserved = set()
if ec_id in self._ec_reserved:
ec_reserved.update(self._ec_reserved[ec_id])
return ec_reserved
-
def Generate(self, existing, generate_one_fn, ec_id):
"""Generate a new resource of this type
"""
return os.path.exists(pathutils.CLUSTER_CONF_FILE)
- def _GenerateOneMAC(self):
- """Generate one mac address
-
- """
- prefix = self._config_data.cluster.mac_prefix
- byte1 = random.randrange(0, 256)
- byte2 = random.randrange(0, 256)
- byte3 = random.randrange(0, 256)
- mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
- return mac
-
@locking.ssynchronized(_config_lock, shared=1)
def GetNdParams(self, node):
"""Get the node params populated with cluster defaults.
"""
return self._config_data.cluster.SimpleFillDP(group.diskparams)
+ def _UnlockedGetNetworkMACPrefix(self, net_uuid):
+ """Return the network mac prefix if it exists or the cluster level default.
+
+ """
+ prefix = None
+ if net_uuid:
+ nobj = self._UnlockedGetNetwork(net_uuid)
+ if nobj.mac_prefix:
+ prefix = nobj.mac_prefix
+
+ return prefix
+
+ def _GenerateOneMAC(self, prefix=None):
+ """Return a function that randomly generates a MAC suffic
+ and appends it to the given prefix. If prefix is not given get
+ the cluster level default.
+
+ """
+ if not prefix:
+ prefix = self._config_data.cluster.mac_prefix
+
+ def GenMac():
+ byte1 = random.randrange(0, 256)
+ byte2 = random.randrange(0, 256)
+ byte3 = random.randrange(0, 256)
+ mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
+ return mac
+
+ return GenMac
+
@locking.ssynchronized(_config_lock, shared=1)
- def GenerateMAC(self, ec_id):
+ def GenerateMAC(self, net_uuid, ec_id):
"""Generate a MAC for an instance.
This should check the current instances for duplicates.
"""
existing = self._AllMACs()
- return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
+ prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
+ gen_mac = self._GenerateOneMAC(prefix)
+ return self._temporary_ids.Generate(existing, gen_mac, ec_id)
@locking.ssynchronized(_config_lock, shared=1)
def ReserveMAC(self, mac, ec_id):
"""
nobj = self._UnlockedGetNetwork(net_uuid)
pool = network.AddressPool(nobj)
- if action == 'reserve':
+ if action == constants.RESERVE_ACTION:
pool.Reserve(address)
- elif action == 'release':
+ elif action == constants.RELEASE_ACTION:
pool.Release(address)
def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
as reserved.
"""
- nobj = self._UnlockedGetNetwork(net_uuid)
- pool = network.AddressPool(nobj)
- self._temporary_ips.Reserve(ec_id, ('release', address, net_uuid))
+ self._temporary_ips.Reserve(ec_id,
+ (constants.RELEASE_ACTION, address, net_uuid))
@locking.ssynchronized(_config_lock, shared=1)
- def ReleaseIp(self, network, address, ec_id):
+ def ReleaseIp(self, net_uuid, address, ec_id):
"""Give a specified IP address back to an IP pool.
This is just a wrapper around _UnlockedReleaseIp.
"""
- net_uuid = self._UnlockedLookupNetwork(network)
if net_uuid:
self._UnlockedReleaseIp(net_uuid, address, ec_id)
@locking.ssynchronized(_config_lock, shared=1)
- def GenerateIp(self, net, ec_id):
+ def GenerateIp(self, net_uuid, ec_id):
"""Find a free IPv4 address for an instance.
"""
- net_uuid = self._UnlockedLookupNetwork(net)
nobj = self._UnlockedGetNetwork(net_uuid)
pool = network.AddressPool(nobj)
- gen_free = pool.GenerateFree()
def gen_one():
try:
- ip = gen_free()
- except StopIteration:
+ ip = pool.GenerateFree()
+ except errors.AddressPoolError:
raise errors.ReservationError("Cannot generate IP. Network is full")
- return ("reserve", ip, net_uuid)
+ return (constants.RESERVE_ACTION, ip, net_uuid)
- _ ,address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
+ _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
return address
def _UnlockedReserveIp(self, net_uuid, address, ec_id):
if isreserved:
raise errors.ReservationError("IP address already in use")
- return self._temporary_ips.Reserve(ec_id, ('reserve', address, net_uuid))
-
+ return self._temporary_ips.Reserve(ec_id,
+ (constants.RESERVE_ACTION,
+ address, net_uuid))
@locking.ssynchronized(_config_lock, shared=1)
- def ReserveIp(self, net, address, ec_id):
+ def ReserveIp(self, net_uuid, address, ec_id):
"""Reserve a given IPv4 address for use by an instance.
"""
- net_uuid = self._UnlockedLookupNetwork(net)
if net_uuid:
return self._UnlockedReserveIp(net_uuid, address, ec_id)
invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
if invalid_hvs:
result.append("enabled hypervisors contains invalid entries: %s" %
- invalid_hvs)
+ utils.CommaJoin(invalid_hvs))
missing_hvp = (set(cluster.enabled_hypervisors) -
set(cluster.hvparams.keys()))
if missing_hvp:
result.append("hypervisor parameters missing for the enabled"
" hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
+ if not cluster.enabled_disk_templates:
+ result.append("enabled disk templates list doesn't have any entries")
+ invalid_disk_templates = set(cluster.enabled_disk_templates) \
+ - constants.DISK_TEMPLATES
+ if invalid_disk_templates:
+ result.append("enabled disk templates list contains invalid entries:"
+ " %s" % utils.CommaJoin(invalid_disk_templates))
+
if cluster.master_node not in data.nodes:
result.append("cluster has invalid primary node '%s'" %
cluster.master_node)
except errors.ConfigurationError, err:
result.append("%s has invalid nicparams: %s" % (owner, err))
- def _helper_ipolicy(owner, params, check_std):
+ def _helper_ipolicy(owner, ipolicy, iscluster):
try:
- objects.InstancePolicy.CheckParameterSyntax(params, check_std)
+ objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
except errors.ConfigurationError, err:
result.append("%s has invalid instance policy: %s" % (owner, err))
-
- def _helper_ispecs(owner, params):
- for key, value in params.items():
- if key in constants.IPOLICY_ISPECS:
- fullkey = "ipolicy/" + key
- _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
+ for key, value in ipolicy.items():
+ if key == constants.ISPECS_MINMAX:
+ _helper_ispecs(owner, "ipolicy/" + key, value)
+ elif key == constants.ISPECS_STD:
+ _helper(owner, "ipolicy/" + key, value,
+ constants.ISPECS_PARAMETER_TYPES)
else:
# FIXME: assuming list type
if key in constants.IPOLICY_PARAMETERS:
" expecting %s, got %s" %
(owner, key, exp_type.__name__, type(value)))
+ def _helper_ispecs(owner, parentkey, params):
+ for (key, value) in params.items():
+ fullkey = "/".join([parentkey, key])
+ _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
+
# check cluster parameters
_helper("cluster", "beparams", cluster.SimpleFillBE({}),
constants.BES_PARAMETER_TYPES)
_helper_nic("cluster", cluster.SimpleFillNIC({}))
_helper("cluster", "ndparams", cluster.SimpleFillND({}),
constants.NDS_PARAMETER_TYPES)
- _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
- _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
+ _helper_ipolicy("cluster", cluster.ipolicy, True)
# per-instance checks
for instance_name in data.instances:
filled, constants.NICS_PARAMETER_TYPES)
_helper_nic(owner, filled)
+ # disk template checks
+ if not instance.disk_template in data.cluster.enabled_disk_templates:
+ result.append("instance '%s' uses the disabled disk template '%s'." %
+ (instance_name, instance.disk_template))
+
# parameter checks
if instance.beparams:
_helper("instance %s" % instance.name, "beparams",
_helper("node %s" % node.name, "ndparams",
cluster.FillND(node, data.nodegroups[node.group]),
constants.NDS_PARAMETER_TYPES)
+ used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
+ if used_globals:
+ result.append("Node '%s' has some global parameters set: %s" %
+ (node.name, utils.CommaJoin(used_globals)))
# nodegroups checks
nodegroups_names = set()
group_name = "group %s" % nodegroup.name
_helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
False)
- _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
if nodegroup.ndparams:
_helper(group_name, "ndparams",
cluster.SimpleFillND(nodegroup.ndparams),
instance = self._UnlockedGetInstanceInfo(instance_name)
for nic in instance.nics:
- if nic.network is not None and nic.ip is not None:
- net_uuid = self._UnlockedLookupNetwork(nic.network)
- if net_uuid:
- # Return all IP addresses to the respective address pools
- self._UnlockedCommitIp('release', net_uuid, nic.ip)
-
+ if nic.network and nic.ip:
+ # Return all IP addresses to the respective address pools
+ self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
del self._config_data.instances[instance_name]
self._config_data.cluster.serial_no += 1
for node_name in nodes)
@locking.ssynchronized(_config_lock, shared=1)
+ def GetInstanceNetworks(self, instance_name):
+ """Returns set of network UUIDs for instance's nics.
+
+ @rtype: frozenset
+
+ """
+ instance = self._UnlockedGetInstanceInfo(instance_name)
+ if not instance:
+ raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
+
+ networks = set()
+ for nic in instance.nics:
+ if nic.network:
+ networks.add(nic.network)
+
+ return frozenset(networks)
+
+ @locking.ssynchronized(_config_lock, shared=1)
def GetMultiInstanceInfo(self, instances):
"""Get the configuration of multiple instances.
return (self._config_data.instances.values() +
self._config_data.nodes.values() +
self._config_data.nodegroups.values() +
+ self._config_data.networks.values() +
[self._config_data.cluster])
def _OpenConfig(self, accept_foreign):
(data.cluster.master_node, self._my_hostname))
raise errors.ConfigurationError(msg)
- # Upgrade configuration if needed
- data.UpgradeConfig()
-
self._config_data = data
# reset the last serial as -1 so that the next write will cause
# ssconf update
self._last_cluster_serial = -1
- # And finally run our (custom) config upgrade sequence
+ # Upgrade configuration if needed
self._UpgradeConfig()
self._cfg_id = utils.GetFileID(path=self._cfg_file)
def _UpgradeConfig(self):
- """Run upgrade steps that cannot be done purely in the objects.
+ """Run any upgrade steps.
- This is because some data elements need uniqueness across the
- whole configuration, etc.
+ This method performs both in-object upgrades and also update some data
+ elements that need uniqueness across the whole configuration or interact
+ with other objects.
@warning: this function will call L{_WriteConfig()}, but also
L{DropECReservations} so it needs to be called only from a
created first, to avoid causing deadlock.
"""
- modified = False
+ # Keep a copy of the persistent part of _config_data to check for changes
+ # Serialization doesn't guarantee order in dictionaries
+ oldconf = copy.deepcopy(self._config_data.ToDict())
+
+ # In-object upgrades
+ self._config_data.UpgradeConfig()
+
for item in self._AllUUIDObjects():
if item.uuid is None:
item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
- modified = True
if not self._config_data.nodegroups:
default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
members=[])
self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
- modified = True
for node in self._config_data.nodes.values():
if not node.group:
node.group = self.LookupNodeGroup(None)
- modified = True
# This is technically *not* an upgrade, but needs to be done both when
# nodegroups are being added, and upon normally loading the config,
# because the members list of a node group is discarded upon
# serializing/deserializing the object.
self._UnlockedAddNodeToGroup(node.name, node.group)
+
+ modified = (oldconf != self._config_data.ToDict())
if modified:
self._WriteConfig()
# This is ok even if it acquires the internal lock, as _UpgradeConfig is
# only called at config init time, without the lock held
self.DropECReservations(_UPGRADE_CONFIG_JID)
+ else:
+ config_errors = self._UnlockedVerifyConfig()
+ if config_errors:
+ errmsg = ("Loaded configuration data is not consistent: %s" %
+ (utils.CommaJoin(config_errors)))
+ logging.critical(errmsg)
def _DistributeConfig(self, feedback_fn):
"""Distribute the configuration to the other nodes.
return self._config_data.HasAnyDiskOfType(dev_type)
@locking.ssynchronized(_config_lock)
- def Update(self, target, feedback_fn):
+ def Update(self, target, feedback_fn, ec_id=None):
"""Notify function to be called after updates.
This function must be called when an object (as returned by
if isinstance(target, objects.Instance):
self._UnlockedReleaseDRBDMinors(target.name)
+ if ec_id is not None:
+ # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
+ self._UnlockedCommitTemporaryIps(ec_id)
+
self._WriteConfig(feedback_fn=feedback_fn)
@locking.ssynchronized(_config_lock)
@locking.ssynchronized(_config_lock, shared=1)
def GetAllNetworksInfo(self):
- """Get the configuration of all networks
+ """Get configuration info of all the networks.
"""
return dict(self._config_data.networks)
"""Get a list of network names
"""
- names = [network.name
- for network in self._config_data.networks.values()]
+ names = [net.name
+ for net in self._config_data.networks.values()]
return names
def _UnlockedGetNetwork(self, uuid):
if check_uuid:
self._EnsureUUID(net, ec_id)
- existing_uuid = self._UnlockedLookupNetwork(net.name)
- if existing_uuid:
- raise errors.OpPrereqError("Desired network name '%s' already"
- " exists as a network (UUID: %s)" %
- (net.name, existing_uuid),
- errors.ECODE_EXISTS)
net.serial_no = 1
self._config_data.networks[net.uuid] = net
self._config_data.cluster.serial_no += 1
@raises errors.OpPrereqError: when the target network cannot be found
"""
+ if target is None:
+ return None
if target in self._config_data.networks:
return target
for net in self._config_data.networks.values():
if net.name == target:
return net.uuid
- return None
+ raise errors.OpPrereqError("Network '%s' not found" % target,
+ errors.ECODE_NOENT)
@locking.ssynchronized(_config_lock, shared=1)
def LookupNetwork(self, target):
self._config_data.cluster.serial_no += 1
self._WriteConfig()
- def _UnlockedGetGroupNetParams(self, net, node):
+ def _UnlockedGetGroupNetParams(self, net_uuid, node):
"""Get the netparams (mode, link) of a network.
Get a network's netparams for a given node.
- @type net: string
- @param net: network name
+ @type net_uuid: string
+ @param net_uuid: network uuid
@type node: string
@param node: node name
@rtype: dict or None
@return: netparams
"""
- net_uuid = self._UnlockedLookupNetwork(net)
- if net_uuid is None:
- return None
-
node_info = self._UnlockedGetNodeInfo(node)
nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
netparams = nodegroup_info.networks.get(net_uuid, None)
return netparams
@locking.ssynchronized(_config_lock, shared=1)
- def GetGroupNetParams(self, net, node):
+ def GetGroupNetParams(self, net_uuid, node):
"""Locking wrapper of _UnlockedGetGroupNetParams()
"""
- return self._UnlockedGetGroupNetParams(net, node)
-
+ return self._UnlockedGetGroupNetParams(net_uuid, node)
@locking.ssynchronized(_config_lock, shared=1)
def CheckIPInNodeGroup(self, ip, node):
- """Check for conflictig IP.
+ """Check IP uniqueness in nodegroup.
+
+ Check networks that are connected in the node's node group
+ if ip is contained in any of them. Used when creating/adding
+ a NIC to ensure uniqueness among nodegroups.
@type ip: string
@param ip: ip address
for net_uuid in nodegroup_info.networks.keys():
net_info = self._UnlockedGetNetwork(net_uuid)
pool = network.AddressPool(net_info)
- if pool._Contains(ip):
+ if pool.Contains(ip):
return (net_info.name, nodegroup_info.networks[net_uuid])
return (None, None)