X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/0c223ea9cb405db6d6a1e75a68fbacda62f3bbeb..fac489a56410b927f19f81ef72755cab9f2b9d29:/lib/ssconf.py diff --git a/lib/ssconf.py b/lib/ssconf.py index b8189d9..11af2a2 100644 --- a/lib/ssconf.py +++ b/lib/ssconf.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008 Google Inc. +# Copyright (C) 2006, 2007, 2008, 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 @@ -27,120 +27,370 @@ configuration data, which is mostly static and available to all nodes. """ import sys +import errno +import logging +from ganeti import compat from ganeti import errors from ganeti import constants from ganeti import utils -from ganeti import serializer +from ganeti import netutils +from ganeti import pathutils SSCONF_LOCK_TIMEOUT = 10 +#: Valid ssconf keys +_VALID_KEYS = compat.UniqueFrozenset([ + constants.SS_CLUSTER_NAME, + constants.SS_CLUSTER_TAGS, + constants.SS_FILE_STORAGE_DIR, + constants.SS_SHARED_FILE_STORAGE_DIR, + constants.SS_MASTER_CANDIDATES, + constants.SS_MASTER_CANDIDATES_IPS, + constants.SS_MASTER_IP, + constants.SS_MASTER_NETDEV, + constants.SS_MASTER_NETMASK, + constants.SS_MASTER_NODE, + constants.SS_NODE_LIST, + constants.SS_NODE_PRIMARY_IPS, + constants.SS_NODE_SECONDARY_IPS, + constants.SS_OFFLINE_NODES, + constants.SS_ONLINE_NODES, + constants.SS_PRIMARY_IP_FAMILY, + constants.SS_INSTANCE_LIST, + constants.SS_RELEASE_VERSION, + constants.SS_HYPERVISOR_LIST, + constants.SS_MAINTAIN_NODE_HEALTH, + constants.SS_UID_POOL, + constants.SS_NODEGROUPS, + constants.SS_NETWORKS, + constants.SS_HVPARAMS_XEN_PVM, + constants.SS_HVPARAMS_XEN_FAKE, + constants.SS_HVPARAMS_XEN_HVM, + constants.SS_HVPARAMS_XEN_KVM, + constants.SS_HVPARAMS_XEN_CHROOT, + constants.SS_HVPARAMS_XEN_LXC, + ]) + +#: Maximum size for ssconf files +_MAX_SIZE = 128 * 1024 + + +def ReadSsconfFile(filename): + """Reads an ssconf file and verifies its size. + + @type filename: string + @param filename: Path to file + @rtype: string + @return: File contents without newlines at the end + @raise RuntimeError: When the file size exceeds L{_MAX_SIZE} -class SimpleConfigReader(object): - """Simple class to read configuration file. + """ + statcb = utils.FileStatHelper() + + data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb) + + if statcb.st.st_size > _MAX_SIZE: + msg = ("File '%s' has a size of %s bytes (up to %s allowed)" % + (filename, statcb.st.st_size, _MAX_SIZE)) + raise RuntimeError(msg) + + return data.rstrip("\n") + + +class SimpleStore(object): + """Interface to static cluster data. + + This is different that the config.ConfigWriter and + SimpleConfigReader classes in that it holds data that will always be + present, even on nodes which don't have all the cluster data. + + Other particularities of the datastore: + - keys are restricted to predefined values """ - def __init__(self, file_name=constants.CLUSTER_CONF_FILE): - """Initializes this class. + def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE): + if cfg_location is None: + self._cfg_dir = pathutils.DATA_DIR + else: + self._cfg_dir = cfg_location + + self._lockfile = _lockfile + + def KeyToFilename(self, key): + """Convert a given key into filename. + + """ + if key not in _VALID_KEYS: + raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" + % str(key)) + + filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key + return filename + + def _ReadFile(self, key, default=None): + """Generic routine to read keys. + + This will read the file which holds the value requested. Errors + will be changed into ConfigurationErrors. + + """ + filename = self.KeyToFilename(key) + try: + return ReadSsconfFile(filename) + except EnvironmentError, err: + if err.errno == errno.ENOENT and default is not None: + return default + raise errors.ConfigurationError("Can't read ssconf file %s: %s" % + (filename, str(err))) + + def ReadAll(self): + """Reads all keys and returns their values. + + @rtype: dict + @return: Dictionary, ssconf key as key, value as value + + """ + result = [] + + for key in _VALID_KEYS: + try: + value = self._ReadFile(key) + except errors.ConfigurationError: + # Ignore non-existing files + pass + else: + result.append((key, value)) + + return dict(result) + + def WriteFiles(self, values, dry_run=False): + """Writes ssconf files used by external scripts. - @type file_name: string - @param file_name: Configuration file path + @type values: dict + @param values: Dictionary of (name, value) + @type dry_run boolean + @param dry_run: Whether to perform a dry run """ - self._file_name = file_name - self._config_data = serializer.Load(utils.ReadFile(file_name)) - # TODO: Error handling + ssconf_lock = utils.FileLock.Open(self._lockfile) + + # Get lock while writing files + ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) + try: + for name, value in values.iteritems(): + if value and not value.endswith("\n"): + value += "\n" + + if len(value) > _MAX_SIZE: + msg = ("Value '%s' has a length of %s bytes, but only up to %s are" + " allowed" % (name, len(value), _MAX_SIZE)) + raise errors.ConfigurationError(msg) + + utils.WriteFile(self.KeyToFilename(name), data=value, + mode=constants.SS_FILE_PERMS, + dry_run=dry_run) + finally: + ssconf_lock.Unlock() + + def GetFileList(self): + """Return the list of all config files. + + This is used for computing node replication data. + + """ + return [self.KeyToFilename(key) for key in _VALID_KEYS] def GetClusterName(self): - return self._config_data["cluster"]["cluster_name"] + """Get the cluster name. - def GetHostKey(self): - return self._config_data["cluster"]["rsahostkeypub"] + """ + return self._ReadFile(constants.SS_CLUSTER_NAME) - def GetMasterNode(self): - return self._config_data["cluster"]["master_node"] + def GetFileStorageDir(self): + """Get the file storage dir. + + """ + return self._ReadFile(constants.SS_FILE_STORAGE_DIR) + + def GetSharedFileStorageDir(self): + """Get the shared file storage dir. + + """ + return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR) + + def GetMasterCandidates(self): + """Return the list of master candidates. + + """ + data = self._ReadFile(constants.SS_MASTER_CANDIDATES) + nl = data.splitlines(False) + return nl + + def GetMasterCandidatesIPList(self): + """Return the list of master candidates' primary IP. + + """ + data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS) + nl = data.splitlines(False) + return nl def GetMasterIP(self): - return self._config_data["cluster"]["master_ip"] + """Get the IP of the master node for this cluster. + + """ + return self._ReadFile(constants.SS_MASTER_IP) def GetMasterNetdev(self): - return self._config_data["cluster"]["master_netdev"] + """Get the netdev to which we'll add the master ip. - def GetFileStorageDir(self): - return self._config_data["cluster"]["file_storage_dir"] + """ + return self._ReadFile(constants.SS_MASTER_NETDEV) + + def GetMasterNetmask(self): + """Get the master netmask. + + """ + try: + return self._ReadFile(constants.SS_MASTER_NETMASK) + except errors.ConfigurationError: + family = self.GetPrimaryIPFamily() + ipcls = netutils.IPAddress.GetClassFromIpFamily(family) + return ipcls.iplen + + def GetMasterNode(self): + """Get the hostname of the master node for this cluster. - def GetHypervisorType(self): - return self._config_data["cluster"]["hypervisor"] + """ + return self._ReadFile(constants.SS_MASTER_NODE) def GetNodeList(self): - return self._config_data["nodes"].keys() + """Return the list of cluster nodes. + + """ + data = self._ReadFile(constants.SS_NODE_LIST) + nl = data.splitlines(False) + return nl - @classmethod - def FromDict(cls, val, cfg_file=constants.CLUSTER_CONF_FILE): - """Alternative construction from a dictionary. + def GetNodePrimaryIPList(self): + """Return the list of cluster nodes' primary IP. """ - obj = SimpleConfigReader.__new__(cls) - obj._config_data = val - obj._file_name = cfg_file - return obj + data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS) + nl = data.splitlines(False) + return nl + def GetNodeSecondaryIPList(self): + """Return the list of cluster nodes' secondary IP. -class SimpleConfigWriter(SimpleConfigReader): - """Simple class to write configuration file. + """ + data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS) + nl = data.splitlines(False) + return nl - """ - def SetMasterNode(self, node): - """Change master node. + def GetNodegroupList(self): + """Return the list of nodegroups. """ - self._config_data["cluster"]["master_node"] = node + data = self._ReadFile(constants.SS_NODEGROUPS) + nl = data.splitlines(False) + return nl - def Save(self): - """Writes configuration file. + def GetNetworkList(self): + """Return the list of networks. - Warning: Doesn't take care of locking or synchronizing with other - processes. + """ + data = self._ReadFile(constants.SS_NETWORKS) + nl = data.splitlines(False) + return nl + + def GetClusterTags(self): + """Return the cluster tags. """ - utils.WriteFile(self._file_name, - data=serializer.Dump(self._config_data), - mode=0600) + data = self._ReadFile(constants.SS_CLUSTER_TAGS) + nl = data.splitlines(False) + return nl + def GetHypervisorList(self): + """Return the list of enabled hypervisors. -def _SsconfPath(name): - return "%s/ssconf_%s" % (constants.DATA_DIR, name) + """ + data = self._ReadFile(constants.SS_HYPERVISOR_LIST) + nl = data.splitlines(False) + return nl + def GetHvparamsForHypervisor(self, hvname): + """Return the hypervisor parameters of the given hypervisor. -def WriteSsconfFiles(file_name): - """Writes legacy ssconf files to be used by external scripts. + @type hvname: string + @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES} + @rtype: dict of strings + @returns: dictionary with hypervisor parameters - @type file_name: string - @param file_name: Path to configuration file + """ + data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname) + lines = data.splitlines(False) + hvparams = {} + for line in lines: + (key, value) = line.split("=") + hvparams[key] = value + return hvparams - """ - ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE) + def GetHvparams(self): + """Return the hypervisor parameters of all hypervisors. - # Read config - cfg = SimpleConfigReader(file_name=file_name) + @rtype: dict of dict of strings + @returns: dictionary mapping hypervisor names to hvparams - # Get lock while writing files - ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) - try: - utils.WriteFile(_SsconfPath("cluster_name"), - data="%s\n" % cfg.GetClusterName()) + """ + all_hvparams = {} + for hv in constants.HYPER_TYPES: + all_hvparams[hv] = self.GetHvparamsForHypervisor(hv) + return all_hvparams + + def GetMaintainNodeHealth(self): + """Return the value of the maintain_node_health option. + + """ + data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH) + # we rely on the bool serialization here + return data == "True" + + def GetUidPool(self): + """Return the user-id pool definition string. - utils.WriteFile(_SsconfPath("master_ip"), - data="%s\n" % cfg.GetMasterIP()) + The separator character is a newline. - utils.WriteFile(_SsconfPath("master_netdev"), - data="%s\n" % cfg.GetMasterNetdev()) + The return value can be parsed using uidpool.ParseUidPool():: - utils.WriteFile(_SsconfPath("master_node"), - data="%s\n" % cfg.GetMasterNode()) - finally: - ssconf_lock.Unlock() + ss = ssconf.SimpleStore() + uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") + + """ + data = self._ReadFile(constants.SS_UID_POOL) + return data + + def GetPrimaryIPFamily(self): + """Return the cluster-wide primary address family. + + """ + try: + return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY, + default=netutils.IP4Address.family)) + except (ValueError, TypeError), err: + raise errors.ConfigurationError("Error while trying to parse primary IP" + " family: %s" % err) + + +def WriteSsconfFiles(values, dry_run=False): + """Update all ssconf files. + + Wrapper around L{SimpleStore.WriteFiles}. + + """ + SimpleStore().WriteFiles(values, dry_run=dry_run) def GetMasterAndMyself(ss=None): @@ -152,10 +402,15 @@ def GetMasterAndMyself(ss=None): The function does not handle any errors, these should be handled in the caller (errors.ConfigurationError, errors.ResolverError). + @param ss: either a sstore.SimpleConfigReader or a + sstore.SimpleStore instance + @rtype: tuple + @return: a tuple (master node name, my own name) + """ if ss is None: - ss = SimpleConfigReader() - return ss.GetMasterNode(), utils.HostInfo().name + ss = SimpleStore() + return ss.GetMasterNode(), netutils.Hostname.GetSysName() def CheckMaster(debug, ss=None): @@ -178,3 +433,35 @@ def CheckMaster(debug, ss=None): if debug: sys.stderr.write("Not master, exiting.\n") sys.exit(constants.EXIT_NOTMASTER) + + +def VerifyClusterName(name, _cfg_location=None): + """Verifies cluster name against a local cluster name. + + @type name: string + @param name: Cluster name + + """ + sstore = SimpleStore(cfg_location=_cfg_location) + + try: + local_name = sstore.GetClusterName() + except errors.ConfigurationError, err: + logging.debug("Can't get local cluster name: %s", err) + else: + if name != local_name: + raise errors.GenericError("Current cluster name is '%s'" % local_name) + + +def VerifyKeys(keys): + """Raises an exception if unknown ssconf keys are given. + + @type keys: sequence + @param keys: Key names to verify + @raise errors.GenericError: When invalid keys were found + + """ + invalid = frozenset(keys) - _VALID_KEYS + if invalid: + raise errors.GenericError("Invalid ssconf keys: %s" % + utils.CommaJoin(sorted(invalid)))