4 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Global Configuration data for Ganeti.
24 This module provides the interface to a special case of cluster
25 configuration data, which is mostly static and available to all nodes.
33 from ganeti import compat
34 from ganeti import errors
35 from ganeti import constants
36 from ganeti import utils
37 from ganeti import netutils
38 from ganeti import pathutils
41 SSCONF_LOCK_TIMEOUT = 10
44 _VALID_KEYS = compat.UniqueFrozenset([
45 constants.SS_CLUSTER_NAME,
46 constants.SS_CLUSTER_TAGS,
47 constants.SS_FILE_STORAGE_DIR,
48 constants.SS_SHARED_FILE_STORAGE_DIR,
49 constants.SS_MASTER_CANDIDATES,
50 constants.SS_MASTER_CANDIDATES_IPS,
51 constants.SS_MASTER_IP,
52 constants.SS_MASTER_NETDEV,
53 constants.SS_MASTER_NETMASK,
54 constants.SS_MASTER_NODE,
55 constants.SS_NODE_LIST,
56 constants.SS_NODE_PRIMARY_IPS,
57 constants.SS_NODE_SECONDARY_IPS,
58 constants.SS_OFFLINE_NODES,
59 constants.SS_ONLINE_NODES,
60 constants.SS_PRIMARY_IP_FAMILY,
61 constants.SS_INSTANCE_LIST,
62 constants.SS_RELEASE_VERSION,
63 constants.SS_HYPERVISOR_LIST,
64 constants.SS_MAINTAIN_NODE_HEALTH,
65 constants.SS_UID_POOL,
66 constants.SS_NODEGROUPS,
67 constants.SS_NETWORKS,
70 #: Maximum size for ssconf files
71 _MAX_SIZE = 128 * 1024
74 def ReadSsconfFile(filename):
75 """Reads an ssconf file and verifies its size.
77 @type filename: string
78 @param filename: Path to file
80 @return: File contents without newlines at the end
81 @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
84 statcb = utils.FileStatHelper()
86 data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
88 if statcb.st.st_size > _MAX_SIZE:
89 msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
90 (filename, statcb.st.st_size, _MAX_SIZE))
91 raise RuntimeError(msg)
93 return data.rstrip("\n")
96 class SimpleStore(object):
97 """Interface to static cluster data.
99 This is different that the config.ConfigWriter and
100 SimpleConfigReader classes in that it holds data that will always be
101 present, even on nodes which don't have all the cluster data.
103 Other particularities of the datastore:
104 - keys are restricted to predefined values
107 def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
108 if cfg_location is None:
109 self._cfg_dir = pathutils.DATA_DIR
111 self._cfg_dir = cfg_location
113 self._lockfile = _lockfile
115 def KeyToFilename(self, key):
116 """Convert a given key into filename.
119 if key not in _VALID_KEYS:
120 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
123 filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
126 def _ReadFile(self, key, default=None):
127 """Generic routine to read keys.
129 This will read the file which holds the value requested. Errors
130 will be changed into ConfigurationErrors.
133 filename = self.KeyToFilename(key)
135 return ReadSsconfFile(filename)
136 except EnvironmentError, err:
137 if err.errno == errno.ENOENT and default is not None:
139 raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
140 (filename, str(err)))
143 """Reads all keys and returns their values.
146 @return: Dictionary, ssconf key as key, value as value
151 for key in _VALID_KEYS:
153 value = self._ReadFile(key)
154 except errors.ConfigurationError:
155 # Ignore non-existing files
158 result.append((key, value))
162 def WriteFiles(self, values, dry_run=False):
163 """Writes ssconf files used by external scripts.
166 @param values: Dictionary of (name, value)
167 @type dry_run boolean
168 @param dry_run: Whether to perform a dry run
171 ssconf_lock = utils.FileLock.Open(self._lockfile)
173 # Get lock while writing files
174 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
176 for name, value in values.iteritems():
177 if value and not value.endswith("\n"):
180 if len(value) > _MAX_SIZE:
181 msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
182 " allowed" % (name, len(value), _MAX_SIZE))
183 raise errors.ConfigurationError(msg)
185 utils.WriteFile(self.KeyToFilename(name), data=value,
186 mode=constants.SS_FILE_PERMS,
191 def GetFileList(self):
192 """Return the list of all config files.
194 This is used for computing node replication data.
197 return [self.KeyToFilename(key) for key in _VALID_KEYS]
199 def GetClusterName(self):
200 """Get the cluster name.
203 return self._ReadFile(constants.SS_CLUSTER_NAME)
205 def GetFileStorageDir(self):
206 """Get the file storage dir.
209 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
211 def GetSharedFileStorageDir(self):
212 """Get the shared file storage dir.
215 return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
217 def GetMasterCandidates(self):
218 """Return the list of master candidates.
221 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
222 nl = data.splitlines(False)
225 def GetMasterCandidatesIPList(self):
226 """Return the list of master candidates' primary IP.
229 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
230 nl = data.splitlines(False)
233 def GetMasterIP(self):
234 """Get the IP of the master node for this cluster.
237 return self._ReadFile(constants.SS_MASTER_IP)
239 def GetMasterNetdev(self):
240 """Get the netdev to which we'll add the master ip.
243 return self._ReadFile(constants.SS_MASTER_NETDEV)
245 def GetMasterNetmask(self):
246 """Get the master netmask.
250 return self._ReadFile(constants.SS_MASTER_NETMASK)
251 except errors.ConfigurationError:
252 family = self.GetPrimaryIPFamily()
253 ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
256 def GetMasterNode(self):
257 """Get the hostname of the master node for this cluster.
260 return self._ReadFile(constants.SS_MASTER_NODE)
262 def GetNodeList(self):
263 """Return the list of cluster nodes.
266 data = self._ReadFile(constants.SS_NODE_LIST)
267 nl = data.splitlines(False)
270 def GetNodePrimaryIPList(self):
271 """Return the list of cluster nodes' primary IP.
274 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
275 nl = data.splitlines(False)
278 def GetNodeSecondaryIPList(self):
279 """Return the list of cluster nodes' secondary IP.
282 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
283 nl = data.splitlines(False)
286 def GetNodegroupList(self):
287 """Return the list of nodegroups.
290 data = self._ReadFile(constants.SS_NODEGROUPS)
291 nl = data.splitlines(False)
294 def GetNetworkList(self):
295 """Return the list of networks.
298 data = self._ReadFile(constants.SS_NETWORKS)
299 nl = data.splitlines(False)
302 def GetClusterTags(self):
303 """Return the cluster tags.
306 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
307 nl = data.splitlines(False)
310 def GetHypervisorList(self):
311 """Return the list of enabled hypervisors.
314 data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
315 nl = data.splitlines(False)
318 def GetMaintainNodeHealth(self):
319 """Return the value of the maintain_node_health option.
322 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
323 # we rely on the bool serialization here
324 return data == "True"
326 def GetUidPool(self):
327 """Return the user-id pool definition string.
329 The separator character is a newline.
331 The return value can be parsed using uidpool.ParseUidPool()::
333 ss = ssconf.SimpleStore()
334 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
337 data = self._ReadFile(constants.SS_UID_POOL)
340 def GetPrimaryIPFamily(self):
341 """Return the cluster-wide primary address family.
345 return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
346 default=netutils.IP4Address.family))
347 except (ValueError, TypeError), err:
348 raise errors.ConfigurationError("Error while trying to parse primary IP"
352 def WriteSsconfFiles(values, dry_run=False):
353 """Update all ssconf files.
355 Wrapper around L{SimpleStore.WriteFiles}.
358 SimpleStore().WriteFiles(values, dry_run=dry_run)
361 def GetMasterAndMyself(ss=None):
362 """Get the master node and my own hostname.
364 This can be either used for a 'soft' check (compared to CheckMaster,
365 which exits) or just for computing both at the same time.
367 The function does not handle any errors, these should be handled in
368 the caller (errors.ConfigurationError, errors.ResolverError).
370 @param ss: either a sstore.SimpleConfigReader or a
371 sstore.SimpleStore instance
373 @return: a tuple (master node name, my own name)
378 return ss.GetMasterNode(), netutils.Hostname.GetSysName()
381 def CheckMaster(debug, ss=None):
382 """Checks the node setup.
384 If this is the master, the function will return. Otherwise it will
385 exit with an exit code based on the node status.
389 master_name, myself = GetMasterAndMyself(ss)
390 except errors.ConfigurationError, err:
391 print "Cluster configuration incomplete: '%s'" % str(err)
392 sys.exit(constants.EXIT_NODESETUP_ERROR)
393 except errors.ResolverError, err:
394 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
395 sys.exit(constants.EXIT_NODESETUP_ERROR)
397 if myself != master_name:
399 sys.stderr.write("Not master, exiting.\n")
400 sys.exit(constants.EXIT_NOTMASTER)
403 def VerifyClusterName(name, _cfg_location=None):
404 """Verifies cluster name against a local cluster name.
407 @param name: Cluster name
410 sstore = SimpleStore(cfg_location=_cfg_location)
413 local_name = sstore.GetClusterName()
414 except errors.ConfigurationError, err:
415 logging.debug("Can't get local cluster name: %s", err)
417 if name != local_name:
418 raise errors.GenericError("Current cluster name is '%s'" % local_name)
421 def VerifyKeys(keys):
422 """Raises an exception if unknown ssconf keys are given.
425 @param keys: Key names to verify
426 @raise errors.GenericError: When invalid keys were found
429 invalid = frozenset(keys) - _VALID_KEYS
431 raise errors.GenericError("Invalid ssconf keys: %s" %
432 utils.CommaJoin(sorted(invalid)))