4 # Copyright (C) 2006, 2007, 2008 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 serializer
39 SSCONF_LOCK_TIMEOUT = 10
41 RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$')
44 class SimpleConfigReader(object):
45 """Simple class to read configuration file.
48 def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
49 """Initializes this class.
51 @type file_name: string
52 @param file_name: Configuration file path
55 self._file_name = file_name
56 self._last_inode = None
57 self._last_mtime = None
58 self._last_size = None
59 # we need a forced reload at class init time, to initialize _last_*
60 self._Load(force=True)
62 def _Load(self, force=False):
63 """Loads (or reloads) the config file.
66 @param force: whether to force the reload without checking the mtime
68 @return: boolean values that says whether we reloaded the configuration or not
69 (because we decided it was already up-to-date)
72 cfg_stat = os.stat(self._file_name)
73 inode = cfg_stat.st_ino
74 mtime = cfg_stat.st_mtime
75 size = cfg_stat.st_size
78 if force or inode != self._last_inode or \
79 mtime > self._last_mtime or \
80 size != self._last_size:
81 self._last_inode = inode
82 self._last_mtime = mtime
83 self._last_size = size
90 self._config_data = serializer.Load(utils.ReadFile(self._file_name))
92 raise errors.ConfigurationError("Cannot read config file %s: %s" %
93 (self._file_name, err))
94 except ValueError, err:
95 raise errors.ConfigurationError("Cannot load config file %s: %s" %
96 (self._file_name, err))
98 self._ip_to_instance = {}
99 for iname in self._config_data['instances']:
100 instance = self._config_data['instances'][iname]
101 for nic in instance['nics']:
102 if 'ip' in nic and nic['ip']:
103 self._ip_to_instance[nic['ip']] = iname
107 # Clients can request a reload of the config file, so we export our internal
108 # _Load function as Reload.
111 def GetClusterName(self):
112 return self._config_data["cluster"]["cluster_name"]
114 def GetHostKey(self):
115 return self._config_data["cluster"]["rsahostkeypub"]
117 def GetMasterNode(self):
118 return self._config_data["cluster"]["master_node"]
120 def GetMasterIP(self):
121 return self._config_data["cluster"]["master_ip"]
123 def GetMasterNetdev(self):
124 return self._config_data["cluster"]["master_netdev"]
126 def GetFileStorageDir(self):
127 return self._config_data["cluster"]["file_storage_dir"]
129 def GetHypervisorType(self):
130 return self._config_data["cluster"]["hypervisor"]
132 def GetNodeList(self):
133 return self._config_data["nodes"].keys()
135 def GetConfigSerialNo(self):
136 return self._config_data["serial_no"]
138 def GetClusterSerialNo(self):
139 return self._config_data["cluster"]["serial_no"]
141 def GetNodeStatusFlags(self, node):
142 """Get a node's status flags
145 @param node: node name
146 @rtype: (bool, bool, bool)
147 @return: (master_candidate, drained, offline) (or None if no such node)
150 if node not in self._config_data["nodes"]:
153 master_candidate = self._config_data["nodes"][node]["master_candidate"]
154 drained = self._config_data["nodes"][node]["drained"]
155 offline = self._config_data["nodes"][node]["offline"]
156 return master_candidate, drained, offline
158 def GetInstanceByIp(self, ip):
159 if ip not in self._ip_to_instance:
161 return self._ip_to_instance[ip]
163 def GetNodePrimaryIp(self, node):
164 """Get a node's primary ip
167 @param node: node name
168 @rtype: string, or None
169 @return: node's primary ip, or None if no such node
172 if node not in self._config_data["nodes"]:
174 return self._config_data["nodes"][node]["primary_ip"]
176 def GetInstancePrimaryNode(self, instance):
177 """Get an instance's primary node
179 @type instance: string
180 @param instance: instance name
181 @rtype: string, or None
182 @return: primary node, or None if no such instance
185 if instance not in self._config_data["instances"]:
187 return self._config_data["instances"][instance]["primary_node"]
190 class SimpleStore(object):
191 """Interface to static cluster data.
193 This is different that the config.ConfigWriter and
194 SimpleConfigReader classes in that it holds data that will always be
195 present, even on nodes which don't have all the cluster data.
197 Other particularities of the datastore:
198 - keys are restricted to predefined values
201 _SS_FILEPREFIX = "ssconf_"
203 constants.SS_CLUSTER_NAME,
204 constants.SS_CLUSTER_TAGS,
205 constants.SS_FILE_STORAGE_DIR,
206 constants.SS_MASTER_CANDIDATES,
207 constants.SS_MASTER_CANDIDATES_IPS,
208 constants.SS_MASTER_IP,
209 constants.SS_MASTER_NETDEV,
210 constants.SS_MASTER_NODE,
211 constants.SS_NODE_LIST,
212 constants.SS_NODE_PRIMARY_IPS,
213 constants.SS_NODE_SECONDARY_IPS,
214 constants.SS_OFFLINE_NODES,
215 constants.SS_ONLINE_NODES,
216 constants.SS_INSTANCE_LIST,
217 constants.SS_RELEASE_VERSION,
221 def __init__(self, cfg_location=None):
222 if cfg_location is None:
223 self._cfg_dir = constants.DATA_DIR
225 self._cfg_dir = cfg_location
227 def KeyToFilename(self, key):
228 """Convert a given key into filename.
231 if key not in self._VALID_KEYS:
232 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
235 filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
238 def _ReadFile(self, key):
239 """Generic routine to read keys.
241 This will read the file which holds the value requested. Errors
242 will be changed into ConfigurationErrors.
245 filename = self.KeyToFilename(key)
247 data = utils.ReadFile(filename, size=self._MAX_SIZE)
248 except EnvironmentError, err:
249 raise errors.ConfigurationError("Can't read from the ssconf file:"
251 data = data.rstrip('\n')
254 def WriteFiles(self, values):
255 """Writes ssconf files used by external scripts.
258 @param values: Dictionary of (name, value)
261 ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
263 # Get lock while writing files
264 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
266 for name, value in values.iteritems():
267 if value and not value.endswith("\n"):
269 utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
273 def GetFileList(self):
274 """Return the list of all config files.
276 This is used for computing node replication data.
279 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
281 def GetClusterName(self):
282 """Get the cluster name.
285 return self._ReadFile(constants.SS_CLUSTER_NAME)
287 def GetFileStorageDir(self):
288 """Get the file storage dir.
291 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
293 def GetMasterCandidates(self):
294 """Return the list of master candidates.
297 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
298 nl = data.splitlines(False)
301 def GetMasterCandidatesIPList(self):
302 """Return the list of master candidates' primary IP.
305 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
306 nl = data.splitlines(False)
309 def GetMasterIP(self):
310 """Get the IP of the master node for this cluster.
313 return self._ReadFile(constants.SS_MASTER_IP)
315 def GetMasterNetdev(self):
316 """Get the netdev to which we'll add the master ip.
319 return self._ReadFile(constants.SS_MASTER_NETDEV)
321 def GetMasterNode(self):
322 """Get the hostname of the master node for this cluster.
325 return self._ReadFile(constants.SS_MASTER_NODE)
327 def GetNodeList(self):
328 """Return the list of cluster nodes.
331 data = self._ReadFile(constants.SS_NODE_LIST)
332 nl = data.splitlines(False)
335 def GetNodePrimaryIPList(self):
336 """Return the list of cluster nodes' primary IP.
339 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
340 nl = data.splitlines(False)
343 def GetNodeSecondaryIPList(self):
344 """Return the list of cluster nodes' secondary IP.
347 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
348 nl = data.splitlines(False)
351 def GetClusterTags(self):
352 """Return the cluster tags.
355 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
356 nl = data.splitlines(False)
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(), utils.HostInfo().name
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 CheckMasterCandidate(debug, ss=None):
403 """Checks the node setup.
405 If this is a master candidate, the function will return. Otherwise it will
406 exit with an exit code based on the node status.
412 myself = utils.HostInfo().name
413 candidates = ss.GetMasterCandidates()
414 except errors.ConfigurationError, err:
415 print "Cluster configuration incomplete: '%s'" % str(err)
416 sys.exit(constants.EXIT_NODESETUP_ERROR)
417 except errors.ResolverError, err:
418 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
419 sys.exit(constants.EXIT_NODESETUP_ERROR)
421 if myself not in candidates:
423 sys.stderr.write("Not master candidate, exiting.\n")
424 sys.exit(constants.EXIT_NOTCANDIDATE)