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 errors
34 from ganeti import constants
35 from ganeti import utils
36 from ganeti import netutils
37 from ganeti import pathutils
40 SSCONF_LOCK_TIMEOUT = 10
43 _VALID_KEYS = frozenset([
44 constants.SS_CLUSTER_NAME,
45 constants.SS_CLUSTER_TAGS,
46 constants.SS_FILE_STORAGE_DIR,
47 constants.SS_SHARED_FILE_STORAGE_DIR,
48 constants.SS_MASTER_CANDIDATES,
49 constants.SS_MASTER_CANDIDATES_IPS,
50 constants.SS_MASTER_IP,
51 constants.SS_MASTER_NETDEV,
52 constants.SS_MASTER_NETMASK,
53 constants.SS_MASTER_NODE,
54 constants.SS_NODE_LIST,
55 constants.SS_NODE_PRIMARY_IPS,
56 constants.SS_NODE_SECONDARY_IPS,
57 constants.SS_OFFLINE_NODES,
58 constants.SS_ONLINE_NODES,
59 constants.SS_PRIMARY_IP_FAMILY,
60 constants.SS_INSTANCE_LIST,
61 constants.SS_RELEASE_VERSION,
62 constants.SS_HYPERVISOR_LIST,
63 constants.SS_MAINTAIN_NODE_HEALTH,
64 constants.SS_UID_POOL,
65 constants.SS_NODEGROUPS,
66 constants.SS_NETWORKS,
69 #: Maximum size for ssconf files
70 _MAX_SIZE = 128 * 1024
73 def ReadSsconfFile(filename):
74 """Reads an ssconf file and verifies its size.
76 @type filename: string
77 @param filename: Path to file
79 @return: File contents without newlines at the end
80 @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
83 statcb = utils.FileStatHelper()
85 data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
87 if statcb.st.st_size > _MAX_SIZE:
88 msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
89 (filename, statcb.st.st_size, _MAX_SIZE))
90 raise RuntimeError(msg)
92 return data.rstrip("\n")
95 class SimpleStore(object):
96 """Interface to static cluster data.
98 This is different that the config.ConfigWriter and
99 SimpleConfigReader classes in that it holds data that will always be
100 present, even on nodes which don't have all the cluster data.
102 Other particularities of the datastore:
103 - keys are restricted to predefined values
106 def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
107 if cfg_location is None:
108 self._cfg_dir = pathutils.DATA_DIR
110 self._cfg_dir = cfg_location
112 self._lockfile = _lockfile
114 def KeyToFilename(self, key):
115 """Convert a given key into filename.
118 if key not in _VALID_KEYS:
119 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
122 filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
125 def _ReadFile(self, key, default=None):
126 """Generic routine to read keys.
128 This will read the file which holds the value requested. Errors
129 will be changed into ConfigurationErrors.
132 filename = self.KeyToFilename(key)
134 return ReadSsconfFile(filename)
135 except EnvironmentError, err:
136 if err.errno == errno.ENOENT and default is not None:
138 raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
139 (filename, str(err)))
142 """Reads all keys and returns their values.
145 @return: Dictionary, ssconf key as key, value as value
150 for key in _VALID_KEYS:
152 value = self._ReadFile(key)
153 except errors.ConfigurationError:
154 # Ignore non-existing files
157 result.append((key, value))
161 def WriteFiles(self, values, dry_run=False):
162 """Writes ssconf files used by external scripts.
165 @param values: Dictionary of (name, value)
166 @type dry_run boolean
167 @param dry_run: Whether to perform a dry run
170 ssconf_lock = utils.FileLock.Open(self._lockfile)
172 # Get lock while writing files
173 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
175 for name, value in values.iteritems():
176 if value and not value.endswith("\n"):
179 if len(value) > _MAX_SIZE:
180 msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
181 " allowed" % (name, len(value), _MAX_SIZE))
182 raise errors.ConfigurationError(msg)
184 utils.WriteFile(self.KeyToFilename(name), data=value,
185 mode=constants.SS_FILE_PERMS,
190 def GetFileList(self):
191 """Return the list of all config files.
193 This is used for computing node replication data.
196 return [self.KeyToFilename(key) for key in _VALID_KEYS]
198 def GetClusterName(self):
199 """Get the cluster name.
202 return self._ReadFile(constants.SS_CLUSTER_NAME)
204 def GetFileStorageDir(self):
205 """Get the file storage dir.
208 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
210 def GetSharedFileStorageDir(self):
211 """Get the shared file storage dir.
214 return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
216 def GetMasterCandidates(self):
217 """Return the list of master candidates.
220 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
221 nl = data.splitlines(False)
224 def GetMasterCandidatesIPList(self):
225 """Return the list of master candidates' primary IP.
228 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
229 nl = data.splitlines(False)
232 def GetMasterIP(self):
233 """Get the IP of the master node for this cluster.
236 return self._ReadFile(constants.SS_MASTER_IP)
238 def GetMasterNetdev(self):
239 """Get the netdev to which we'll add the master ip.
242 return self._ReadFile(constants.SS_MASTER_NETDEV)
244 def GetMasterNetmask(self):
245 """Get the master netmask.
249 return self._ReadFile(constants.SS_MASTER_NETMASK)
250 except errors.ConfigurationError:
251 family = self.GetPrimaryIPFamily()
252 ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
255 def GetMasterNode(self):
256 """Get the hostname of the master node for this cluster.
259 return self._ReadFile(constants.SS_MASTER_NODE)
261 def GetNodeList(self):
262 """Return the list of cluster nodes.
265 data = self._ReadFile(constants.SS_NODE_LIST)
266 nl = data.splitlines(False)
269 def GetNodePrimaryIPList(self):
270 """Return the list of cluster nodes' primary IP.
273 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
274 nl = data.splitlines(False)
277 def GetNodeSecondaryIPList(self):
278 """Return the list of cluster nodes' secondary IP.
281 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
282 nl = data.splitlines(False)
285 def GetNodegroupList(self):
286 """Return the list of nodegroups.
289 data = self._ReadFile(constants.SS_NODEGROUPS)
290 nl = data.splitlines(False)
293 def GetNetworkList(self):
294 """Return the list of networks.
297 data = self._ReadFile(constants.SS_NETWORKS)
298 nl = data.splitlines(False)
301 def GetClusterTags(self):
302 """Return the cluster tags.
305 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
306 nl = data.splitlines(False)
309 def GetHypervisorList(self):
310 """Return the list of enabled hypervisors.
313 data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
314 nl = data.splitlines(False)
317 def GetMaintainNodeHealth(self):
318 """Return the value of the maintain_node_health option.
321 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
322 # we rely on the bool serialization here
323 return data == "True"
325 def GetUidPool(self):
326 """Return the user-id pool definition string.
328 The separator character is a newline.
330 The return value can be parsed using uidpool.ParseUidPool()::
332 ss = ssconf.SimpleStore()
333 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
336 data = self._ReadFile(constants.SS_UID_POOL)
339 def GetPrimaryIPFamily(self):
340 """Return the cluster-wide primary address family.
344 return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
345 default=netutils.IP4Address.family))
346 except (ValueError, TypeError), err:
347 raise errors.ConfigurationError("Error while trying to parse primary IP"
351 def WriteSsconfFiles(values, dry_run=False):
352 """Update all ssconf files.
354 Wrapper around L{SimpleStore.WriteFiles}.
357 SimpleStore().WriteFiles(values, dry_run=dry_run)
360 def GetMasterAndMyself(ss=None):
361 """Get the master node and my own hostname.
363 This can be either used for a 'soft' check (compared to CheckMaster,
364 which exits) or just for computing both at the same time.
366 The function does not handle any errors, these should be handled in
367 the caller (errors.ConfigurationError, errors.ResolverError).
369 @param ss: either a sstore.SimpleConfigReader or a
370 sstore.SimpleStore instance
372 @return: a tuple (master node name, my own name)
377 return ss.GetMasterNode(), netutils.Hostname.GetSysName()
380 def CheckMaster(debug, ss=None):
381 """Checks the node setup.
383 If this is the master, the function will return. Otherwise it will
384 exit with an exit code based on the node status.
388 master_name, myself = GetMasterAndMyself(ss)
389 except errors.ConfigurationError, err:
390 print "Cluster configuration incomplete: '%s'" % str(err)
391 sys.exit(constants.EXIT_NODESETUP_ERROR)
392 except errors.ResolverError, err:
393 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
394 sys.exit(constants.EXIT_NODESETUP_ERROR)
396 if myself != master_name:
398 sys.stderr.write("Not master, exiting.\n")
399 sys.exit(constants.EXIT_NOTMASTER)
402 def VerifyClusterName(name, _cfg_location=None):
403 """Verifies cluster name against a local cluster name.
406 @param name: Cluster name
409 sstore = SimpleStore(cfg_location=_cfg_location)
412 local_name = sstore.GetClusterName()
413 except errors.ConfigurationError, err:
414 logging.debug("Can't get local cluster name: %s", err)
416 if name != local_name:
417 raise errors.GenericError("Current cluster name is '%s'" % local_name)
420 def VerifyKeys(keys):
421 """Raises an exception if unknown ssconf keys are given.
424 @param keys: Key names to verify
425 @raise errors.GenericError: When invalid keys were found
428 invalid = frozenset(keys) - _VALID_KEYS
430 raise errors.GenericError("Invalid ssconf keys: %s" %
431 utils.CommaJoin(sorted(invalid)))