X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/3c286190a94c93de556e82fd2d84362b9e52a8d3..62810f079e47025145fa4f2b94c1e5731f8ee031:/lib/config.py diff --git a/lib/config.py b/lib/config.py index 2cd0646..ad99684 100644 --- a/lib/config.py +++ b/lib/config.py @@ -1,7 +1,7 @@ # # -# 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 @@ -34,12 +34,12 @@ much memory. # pylint: disable=R0904 # R0904: Too many public methods +import copy import os import random import logging import time import itertools -from functools import wraps from ganeti import errors from ganeti import locking @@ -110,6 +110,11 @@ class TemporaryReservationManager: 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]) @@ -162,17 +167,6 @@ def _CheckInstanceDiskIvNames(disks): return result -def _GenerateMACSuffix(): - """Generate one mac address - - """ - byte1 = random.randrange(0, 256) - byte2 = random.randrange(0, 256) - byte3 = random.randrange(0, 256) - suffix = "%02x:%02x:%02x" % (byte1, byte2, byte3) - return suffix - - class ConfigWriter: """The interface to the cluster configuration. @@ -230,21 +224,6 @@ class ConfigWriter: """ return os.path.exists(pathutils.CLUSTER_CONF_FILE) - def _GenerateMACPrefix(self, net=None): - def _get_mac_prefix(view_func): - def _decorator(*args, **kwargs): - prefix = self._config_data.cluster.mac_prefix - if net: - net_uuid = self._UnlockedLookupNetwork(net) - if net_uuid: - nobj = self._UnlockedGetNetwork(net_uuid) - if nobj.mac_prefix: - prefix = nobj.mac_prefix - suffix = view_func(*args, **kwargs) - return prefix + ':' + suffix - return wraps(view_func)(_decorator) - return _get_mac_prefix - @locking.ssynchronized(_config_lock, shared=1) def GetNdParams(self, node): """Get the node params populated with cluster defaults. @@ -291,15 +270,46 @@ class ConfigWriter: """ 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, net, 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() - gen_mac = self._GenerateMACPrefix(net)(_GenerateMACSuffix) + 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) @@ -331,9 +341,9 @@ class ConfigWriter: """ 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): @@ -343,35 +353,33 @@ class ConfigWriter: as reserved. """ - 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, net, 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(net) 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) return address @@ -389,14 +397,15 @@ class ConfigWriter: 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) @@ -436,6 +445,35 @@ class ConfigWriter: lvnames.update(lv_list) return lvnames + def _AllDisks(self): + """Compute the list of all Disks (recursively, including children). + + """ + def DiskAndAllChildren(disk): + """Returns a list containing the given disk and all of his children. + + """ + disks = [disk] + if disk.children: + for child_disk in disk.children: + disks.extend(DiskAndAllChildren(child_disk)) + return disks + + disks = [] + for instance in self._config_data.instances.values(): + for disk in instance.disks: + disks.extend(DiskAndAllChildren(disk)) + return disks + + def _AllNICs(self): + """Compute the list of all NICs. + + """ + nics = [] + for instance in self._config_data.instances.values(): + nics.extend(instance.nics) + return nics + def _AllIDs(self, include_temporary): """Compute the list of all UUIDs and names we have. @@ -568,13 +606,21 @@ class ConfigWriter: 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) @@ -591,17 +637,18 @@ class ConfigWriter: 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: + for k in range(len(value)): + _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k]) + 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: @@ -613,6 +660,11 @@ class ConfigWriter: " 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) @@ -621,8 +673,7 @@ class ConfigWriter: _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: @@ -650,6 +701,11 @@ class ConfigWriter: 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", @@ -731,6 +787,10 @@ class ConfigWriter: _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() @@ -749,7 +809,6 @@ class ConfigWriter: 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), @@ -1131,7 +1190,7 @@ class ConfigWriter: return self._config_data.cluster.enabled_hypervisors[0] @locking.ssynchronized(_config_lock, shared=1) - def GetHostKey(self): + def GetRsaHostKey(self): """Return the rsa hostkey from the config. @rtype: string @@ -1141,6 +1200,16 @@ class ConfigWriter: return self._config_data.cluster.rsahostkeypub @locking.ssynchronized(_config_lock, shared=1) + def GetDsaHostKey(self): + """Return the dsa hostkey from the config. + + @rtype: string + @return: the dsa hostkey + + """ + return self._config_data.cluster.dsahostkeypub + + @locking.ssynchronized(_config_lock, shared=1) def GetDefaultIAllocator(self): """Get the default instance allocator for this cluster. @@ -1388,19 +1457,27 @@ class ConfigWriter: raise errors.ConfigurationError("Cannot add '%s': UUID %s already" " in use" % (item.name, item.uuid)) - def _SetInstanceStatus(self, instance_name, status): + def _SetInstanceStatus(self, instance_name, status, disks_active): """Set the instance's status to a given value. """ - assert status in constants.ADMINST_ALL, \ - "Invalid status '%s' passed to SetInstanceStatus" % (status,) - if instance_name not in self._config_data.instances: raise errors.ConfigurationError("Unknown instance '%s'" % instance_name) instance = self._config_data.instances[instance_name] - if instance.admin_state != status: + + if status is None: + status = instance.admin_state + if disks_active is None: + disks_active = instance.disks_active + + assert status in constants.ADMINST_ALL, \ + "Invalid status '%s' passed to SetInstanceStatus" % (status,) + + if instance.admin_state != status or \ + instance.disks_active != disks_active: instance.admin_state = status + instance.disks_active = disks_active instance.serial_no += 1 instance.mtime = time.time() self._WriteConfig() @@ -1409,15 +1486,19 @@ class ConfigWriter: def MarkInstanceUp(self, instance_name): """Mark the instance status to up in the config. + This also sets the instance disks active flag. + """ - self._SetInstanceStatus(instance_name, constants.ADMINST_UP) + self._SetInstanceStatus(instance_name, constants.ADMINST_UP, True) @locking.ssynchronized(_config_lock) def MarkInstanceOffline(self, instance_name): """Mark the instance status to down in the config. + This also clears the instance disks active flag. + """ - self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE) + self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE, False) @locking.ssynchronized(_config_lock) def RemoveInstance(self, instance_name): @@ -1437,11 +1518,9 @@ class ConfigWriter: 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 @@ -1485,8 +1564,25 @@ class ConfigWriter: def MarkInstanceDown(self, instance_name): """Mark the status of an instance to down in the configuration. + This does not touch the instance disks active flag, as shut down instances + can still have active disks. + """ - self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN) + self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN, None) + + @locking.ssynchronized(_config_lock) + def MarkInstanceDisksActive(self, instance_name): + """Mark the status of instance disks active. + + """ + self._SetInstanceStatus(instance_name, None, True) + + @locking.ssynchronized(_config_lock) + def MarkInstanceDisksInactive(self, instance_name): + """Mark the status of instance disks inactive. + + """ + self._SetInstanceStatus(instance_name, None, False) def _UnlockedGetInstanceList(self): """Get the list of instances. @@ -1560,6 +1656,24 @@ class ConfigWriter: 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. @@ -1992,6 +2106,9 @@ class ConfigWriter: return (self._config_data.instances.values() + self._config_data.nodes.values() + self._config_data.nodegroups.values() + + self._config_data.networks.values() + + self._AllDisks() + + self._AllNICs() + [self._config_data.cluster]) def _OpenConfig(self, accept_foreign): @@ -2020,24 +2137,22 @@ class ConfigWriter: (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 @@ -2046,31 +2161,42 @@ class ConfigWriter: 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. @@ -2378,7 +2504,7 @@ class ConfigWriter: @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) @@ -2456,13 +2582,8 @@ class ConfigWriter: 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 + net.ctime = net.mtime = time.time() self._config_data.networks[net.uuid] = net self._config_data.cluster.serial_no += 1 @@ -2476,12 +2597,15 @@ class ConfigWriter: @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): @@ -2514,23 +2638,19 @@ class ConfigWriter: 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) @@ -2538,15 +2658,19 @@ class ConfigWriter: 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