X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/920b5878175bde46b1e62bd63a5e0434ee037a8c..dc2cc657c8c6154b35731ac8166fcf25b9a4f244:/lib/ssconf.py diff --git a/lib/ssconf.py b/lib/ssconf.py index 0792186..2399d81 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 @@ -28,16 +28,20 @@ configuration data, which is mostly static and available to all nodes. import sys import re +import os +import errno from ganeti import errors from ganeti import constants from ganeti import utils from ganeti import serializer +from ganeti import objects +from ganeti import netutils SSCONF_LOCK_TIMEOUT = 10 -RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$') +RE_VALID_SSCONF_NAME = re.compile(r"^[-_a-z0-9]+$") class SimpleConfigReader(object): @@ -52,8 +56,86 @@ class SimpleConfigReader(object): """ self._file_name = file_name - self._config_data = serializer.Load(utils.ReadFile(file_name)) - # TODO: Error handling + self._last_inode = None + self._last_mtime = None + self._last_size = None + + self._config_data = None + self._inst_ips_by_link = None + self._ip_to_inst_by_link = None + self._instances_ips = None + self._mc_primary_ips = None + self._nodes_primary_ips = None + + # we need a forced reload at class init time, to initialize _last_* + self._Load(force=True) + + def _Load(self, force=False): + """Loads (or reloads) the config file. + + @type force: boolean + @param force: whether to force the reload without checking the mtime + @rtype: boolean + @return: boolean value that says whether we reloaded the configuration or + not (because we decided it was already up-to-date) + + """ + try: + cfg_stat = os.stat(self._file_name) + except EnvironmentError, err: + raise errors.ConfigurationError("Cannot stat config file %s: %s" % + (self._file_name, err)) + inode = cfg_stat.st_ino + mtime = cfg_stat.st_mtime + size = cfg_stat.st_size + + if (force or inode != self._last_inode or + mtime > self._last_mtime or + size != self._last_size): + self._last_inode = inode + self._last_mtime = mtime + self._last_size = size + else: + # Don't reload + return False + + try: + self._config_data = serializer.Load(utils.ReadFile(self._file_name)) + except EnvironmentError, err: + raise errors.ConfigurationError("Cannot read config file %s: %s" % + (self._file_name, err)) + except ValueError, err: + raise errors.ConfigurationError("Cannot load config file %s: %s" % + (self._file_name, err)) + + self._ip_to_inst_by_link = {} + self._instances_ips = [] + self._inst_ips_by_link = {} + c_nparams = self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT] + for iname in self._config_data["instances"]: + instance = self._config_data["instances"][iname] + for nic in instance["nics"]: + if "ip" in nic and nic["ip"]: + params = objects.FillDict(c_nparams, nic["nicparams"]) + if not params["link"] in self._inst_ips_by_link: + self._inst_ips_by_link[params["link"]] = [] + self._ip_to_inst_by_link[params["link"]] = {} + self._ip_to_inst_by_link[params["link"]][nic["ip"]] = iname + self._inst_ips_by_link[params["link"]].append(nic["ip"]) + + self._nodes_primary_ips = [] + self._mc_primary_ips = [] + for node_name in self._config_data["nodes"]: + node = self._config_data["nodes"][node_name] + self._nodes_primary_ips.append(node["primary_ip"]) + if node["master_candidate"]: + self._mc_primary_ips.append(node["primary_ip"]) + + return True + + # Clients can request a reload of the config file, so we export our internal + # _Load function as Reload. + Reload = _Load def GetClusterName(self): return self._config_data["cluster"]["cluster_name"] @@ -70,46 +152,114 @@ class SimpleConfigReader(object): def GetMasterNetdev(self): return self._config_data["cluster"]["master_netdev"] + def GetMasterNetmask(self): + return self._config_data["cluster"]["master_netmask"] + def GetFileStorageDir(self): return self._config_data["cluster"]["file_storage_dir"] - def GetHypervisorType(self): - return self._config_data["cluster"]["hypervisor"] + def GetSharedFileStorageDir(self): + return self._config_data["cluster"]["shared_file_storage_dir"] def GetNodeList(self): return self._config_data["nodes"].keys() - @classmethod - def FromDict(cls, val, cfg_file=constants.CLUSTER_CONF_FILE): - """Alternative construction from a dictionary. + def GetConfigSerialNo(self): + return self._config_data["serial_no"] + + def GetClusterSerialNo(self): + return self._config_data["cluster"]["serial_no"] + + def GetDefaultNicParams(self): + return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT] + + def GetDefaultNicLink(self): + return self.GetDefaultNicParams()[constants.NIC_LINK] + + def GetNodeStatusFlags(self, node): + """Get a node's status flags + + @type node: string + @param node: node name + @rtype: (bool, bool, bool) + @return: (master_candidate, drained, offline) (or None if no such node) """ - obj = SimpleConfigReader.__new__(cls) - obj._config_data = val - obj._file_name = cfg_file - return obj + if node not in self._config_data["nodes"]: + return None + master_candidate = self._config_data["nodes"][node]["master_candidate"] + drained = self._config_data["nodes"][node]["drained"] + offline = self._config_data["nodes"][node]["offline"] + return master_candidate, drained, offline -class SimpleConfigWriter(SimpleConfigReader): - """Simple class to write configuration file. + def GetInstanceByLinkIp(self, ip, link): + """Get instance name from its link and ip address. - """ - def SetMasterNode(self, node): - """Change master node. + @type ip: string + @param ip: ip address + @type link: string + @param link: nic link + @rtype: string + @return: instance name """ - self._config_data["cluster"]["master_node"] = node + if not link: + link = self.GetDefaultNicLink() + if not link in self._ip_to_inst_by_link: + return None + if not ip in self._ip_to_inst_by_link[link]: + return None + return self._ip_to_inst_by_link[link][ip] + + def GetNodePrimaryIp(self, node): + """Get a node's primary ip + + @type node: string + @param node: node name + @rtype: string, or None + @return: node's primary ip, or None if no such node - def Save(self): - """Writes configuration file. + """ + if node not in self._config_data["nodes"]: + return None + return self._config_data["nodes"][node]["primary_ip"] + + def GetInstancePrimaryNode(self, instance): + """Get an instance's primary node + + @type instance: string + @param instance: instance name + @rtype: string, or None + @return: primary node, or None if no such instance + + """ + if instance not in self._config_data["instances"]: + return None + return self._config_data["instances"][instance]["primary_node"] - Warning: Doesn't take care of locking or synchronizing with other - processes. + def GetNodesPrimaryIps(self): + return self._nodes_primary_ips + + def GetMasterCandidatesPrimaryIps(self): + return self._mc_primary_ips + + def GetInstancesIps(self, link): + """Get list of nic ips connected to a certain link. + + @type link: string + @param link: nic link + @rtype: list + @return: list of ips connected to that link """ - utils.WriteFile(self._file_name, - data=serializer.Dump(self._config_data), - mode=0600) + if not link: + link = self.GetDefaultNicLink() + + if link in self._inst_ips_by_link: + return self._inst_ips_by_link[link] + else: + return [] class SimpleStore(object): @@ -123,20 +273,29 @@ class SimpleStore(object): - keys are restricted to predefined values """ - _SS_FILEPREFIX = "ssconf_" _VALID_KEYS = ( 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, ) _MAX_SIZE = 131072 @@ -154,10 +313,10 @@ class SimpleStore(object): raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" % str(key)) - filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key + filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key return filename - def _ReadFile(self, key): + def _ReadFile(self, key, default=None): """Generic routine to read keys. This will read the file which holds the value requested. Errors @@ -168,9 +327,11 @@ class SimpleStore(object): try: data = utils.ReadFile(filename, size=self._MAX_SIZE) except EnvironmentError, err: + if err.errno == errno.ENOENT and default is not None: + return default raise errors.ConfigurationError("Can't read from the ssconf file:" " '%s'" % str(err)) - data = data.rstrip('\n') + data = data.rstrip("\n") return data def WriteFiles(self, values): @@ -180,7 +341,7 @@ class SimpleStore(object): @param values: Dictionary of (name, value) """ - ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE) + ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE) # Get lock while writing files ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) @@ -188,7 +349,11 @@ class SimpleStore(object): for name, value in values.iteritems(): if value and not value.endswith("\n"): value += "\n" - utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444) + if len(value) > self._MAX_SIZE: + raise errors.ConfigurationError("ssconf file %s above maximum size" % + name) + utils.WriteFile(self.KeyToFilename(name), data=value, + mode=constants.SS_FILE_PERMS) finally: ssconf_lock.Unlock() @@ -212,6 +377,12 @@ class SimpleStore(object): """ 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. @@ -220,6 +391,14 @@ class SimpleStore(object): 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): """Get the IP of the master node for this cluster. @@ -232,6 +411,17 @@ class SimpleStore(object): """ 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. @@ -246,6 +436,30 @@ class SimpleStore(object): nl = data.splitlines(False) return nl + def GetNodePrimaryIPList(self): + """Return the list of cluster nodes' primary IP. + + """ + 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. + + """ + data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS) + nl = data.splitlines(False) + return nl + + def GetNodegroupList(self): + """Return the list of nodegroups. + + """ + data = self._ReadFile(constants.SS_NODEGROUPS) + nl = data.splitlines(False) + return nl + def GetClusterTags(self): """Return the cluster tags. @@ -254,6 +468,47 @@ class SimpleStore(object): nl = data.splitlines(False) return nl + def GetHypervisorList(self): + """Return the list of enabled hypervisors. + + """ + data = self._ReadFile(constants.SS_HYPERVISOR_LIST) + nl = data.splitlines(False) + return nl + + 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. + + The separator character is a newline. + + The return value can be parsed using uidpool.ParseUidPool():: + + 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 GetMasterAndMyself(ss=None): """Get the master node and my own hostname. @@ -272,7 +527,7 @@ def GetMasterAndMyself(ss=None): """ if ss is None: ss = SimpleStore() - return ss.GetMasterNode(), utils.HostInfo().name + return ss.GetMasterNode(), netutils.Hostname.GetSysName() def CheckMaster(debug, ss=None):