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 value that says whether we reloaded the configuration or
69 not (because we decided it was already up-to-date)
73 cfg_stat = os.stat(self._file_name)
74 except EnvironmentError, err:
75 raise errors.ConfigurationError("Cannot stat config file %s: %s" %
76 (self._file_name, err))
77 inode = cfg_stat.st_ino
78 mtime = cfg_stat.st_mtime
79 size = cfg_stat.st_size
82 if force or inode != self._last_inode or \
83 mtime > self._last_mtime or \
84 size != self._last_size:
85 self._last_inode = inode
86 self._last_mtime = mtime
87 self._last_size = size
94 self._config_data = serializer.Load(utils.ReadFile(self._file_name))
95 except EnvironmentError, err:
96 raise errors.ConfigurationError("Cannot read config file %s: %s" %
97 (self._file_name, err))
98 except ValueError, err:
99 raise errors.ConfigurationError("Cannot load config file %s: %s" %
100 (self._file_name, err))
102 self._ip_to_instance = {}
103 self._instances_ips = []
104 for iname in self._config_data['instances']:
105 instance = self._config_data['instances'][iname]
106 for nic in instance['nics']:
107 if 'ip' in nic and nic['ip']:
108 self._instances_ips.append(nic['ip'])
109 self._ip_to_instance[nic['ip']] = iname
111 self._nodes_primary_ips = []
112 self._mc_primary_ips = []
113 for node_name in self._config_data["nodes"]:
114 node = self._config_data["nodes"][node_name]
115 self._nodes_primary_ips.append(node["primary_ip"])
116 if node["master_candidate"]:
117 self._mc_primary_ips.append(node["primary_ip"])
121 # Clients can request a reload of the config file, so we export our internal
122 # _Load function as Reload.
125 def GetClusterName(self):
126 return self._config_data["cluster"]["cluster_name"]
128 def GetHostKey(self):
129 return self._config_data["cluster"]["rsahostkeypub"]
131 def GetMasterNode(self):
132 return self._config_data["cluster"]["master_node"]
134 def GetMasterIP(self):
135 return self._config_data["cluster"]["master_ip"]
137 def GetMasterNetdev(self):
138 return self._config_data["cluster"]["master_netdev"]
140 def GetFileStorageDir(self):
141 return self._config_data["cluster"]["file_storage_dir"]
143 def GetNodeList(self):
144 return self._config_data["nodes"].keys()
146 def GetConfigSerialNo(self):
147 return self._config_data["serial_no"]
149 def GetClusterSerialNo(self):
150 return self._config_data["cluster"]["serial_no"]
152 def GetNodeStatusFlags(self, node):
153 """Get a node's status flags
156 @param node: node name
157 @rtype: (bool, bool, bool)
158 @return: (master_candidate, drained, offline) (or None if no such node)
161 if node not in self._config_data["nodes"]:
164 master_candidate = self._config_data["nodes"][node]["master_candidate"]
165 drained = self._config_data["nodes"][node]["drained"]
166 offline = self._config_data["nodes"][node]["offline"]
167 return master_candidate, drained, offline
169 def GetInstanceByIp(self, ip):
170 if ip not in self._ip_to_instance:
172 return self._ip_to_instance[ip]
174 def GetNodePrimaryIp(self, node):
175 """Get a node's primary ip
178 @param node: node name
179 @rtype: string, or None
180 @return: node's primary ip, or None if no such node
183 if node not in self._config_data["nodes"]:
185 return self._config_data["nodes"][node]["primary_ip"]
187 def GetInstancePrimaryNode(self, instance):
188 """Get an instance's primary node
190 @type instance: string
191 @param instance: instance name
192 @rtype: string, or None
193 @return: primary node, or None if no such instance
196 if instance not in self._config_data["instances"]:
198 return self._config_data["instances"][instance]["primary_node"]
200 def GetNodesPrimaryIps(self):
201 return self._nodes_primary_ips
203 def GetMasterCandidatesPrimaryIps(self):
204 return self._mc_primary_ips
206 def GetInstancesIps(self):
207 return self._instances_ips
210 class SimpleStore(object):
211 """Interface to static cluster data.
213 This is different that the config.ConfigWriter and
214 SimpleConfigReader classes in that it holds data that will always be
215 present, even on nodes which don't have all the cluster data.
217 Other particularities of the datastore:
218 - keys are restricted to predefined values
221 _SS_FILEPREFIX = "ssconf_"
223 constants.SS_CLUSTER_NAME,
224 constants.SS_CLUSTER_TAGS,
225 constants.SS_FILE_STORAGE_DIR,
226 constants.SS_MASTER_CANDIDATES,
227 constants.SS_MASTER_CANDIDATES_IPS,
228 constants.SS_MASTER_IP,
229 constants.SS_MASTER_NETDEV,
230 constants.SS_MASTER_NODE,
231 constants.SS_NODE_LIST,
232 constants.SS_NODE_PRIMARY_IPS,
233 constants.SS_NODE_SECONDARY_IPS,
234 constants.SS_OFFLINE_NODES,
235 constants.SS_ONLINE_NODES,
236 constants.SS_INSTANCE_LIST,
237 constants.SS_RELEASE_VERSION,
241 def __init__(self, cfg_location=None):
242 if cfg_location is None:
243 self._cfg_dir = constants.DATA_DIR
245 self._cfg_dir = cfg_location
247 def KeyToFilename(self, key):
248 """Convert a given key into filename.
251 if key not in self._VALID_KEYS:
252 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
255 filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
258 def _ReadFile(self, key):
259 """Generic routine to read keys.
261 This will read the file which holds the value requested. Errors
262 will be changed into ConfigurationErrors.
265 filename = self.KeyToFilename(key)
267 data = utils.ReadFile(filename, size=self._MAX_SIZE)
268 except EnvironmentError, err:
269 raise errors.ConfigurationError("Can't read from the ssconf file:"
271 data = data.rstrip('\n')
274 def WriteFiles(self, values):
275 """Writes ssconf files used by external scripts.
278 @param values: Dictionary of (name, value)
281 ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
283 # Get lock while writing files
284 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
286 for name, value in values.iteritems():
287 if value and not value.endswith("\n"):
289 utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
293 def GetFileList(self):
294 """Return the list of all config files.
296 This is used for computing node replication data.
299 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
301 def GetClusterName(self):
302 """Get the cluster name.
305 return self._ReadFile(constants.SS_CLUSTER_NAME)
307 def GetFileStorageDir(self):
308 """Get the file storage dir.
311 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
313 def GetMasterCandidates(self):
314 """Return the list of master candidates.
317 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
318 nl = data.splitlines(False)
321 def GetMasterCandidatesIPList(self):
322 """Return the list of master candidates' primary IP.
325 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
326 nl = data.splitlines(False)
329 def GetMasterIP(self):
330 """Get the IP of the master node for this cluster.
333 return self._ReadFile(constants.SS_MASTER_IP)
335 def GetMasterNetdev(self):
336 """Get the netdev to which we'll add the master ip.
339 return self._ReadFile(constants.SS_MASTER_NETDEV)
341 def GetMasterNode(self):
342 """Get the hostname of the master node for this cluster.
345 return self._ReadFile(constants.SS_MASTER_NODE)
347 def GetNodeList(self):
348 """Return the list of cluster nodes.
351 data = self._ReadFile(constants.SS_NODE_LIST)
352 nl = data.splitlines(False)
355 def GetNodePrimaryIPList(self):
356 """Return the list of cluster nodes' primary IP.
359 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
360 nl = data.splitlines(False)
363 def GetNodeSecondaryIPList(self):
364 """Return the list of cluster nodes' secondary IP.
367 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
368 nl = data.splitlines(False)
371 def GetClusterTags(self):
372 """Return the cluster tags.
375 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
376 nl = data.splitlines(False)
380 def GetMasterAndMyself(ss=None):
381 """Get the master node and my own hostname.
383 This can be either used for a 'soft' check (compared to CheckMaster,
384 which exits) or just for computing both at the same time.
386 The function does not handle any errors, these should be handled in
387 the caller (errors.ConfigurationError, errors.ResolverError).
389 @param ss: either a sstore.SimpleConfigReader or a
390 sstore.SimpleStore instance
392 @return: a tuple (master node name, my own name)
397 return ss.GetMasterNode(), utils.HostInfo().name
400 def CheckMaster(debug, ss=None):
401 """Checks the node setup.
403 If this is the master, the function will return. Otherwise it will
404 exit with an exit code based on the node status.
408 master_name, myself = GetMasterAndMyself(ss)
409 except errors.ConfigurationError, err:
410 print "Cluster configuration incomplete: '%s'" % str(err)
411 sys.exit(constants.EXIT_NODESETUP_ERROR)
412 except errors.ResolverError, err:
413 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
414 sys.exit(constants.EXIT_NODESETUP_ERROR)
416 if myself != master_name:
418 sys.stderr.write("Not master, exiting.\n")
419 sys.exit(constants.EXIT_NOTMASTER)
422 def CheckMasterCandidate(debug, ss=None):
423 """Checks the node setup.
425 If this is a master candidate, the function will return. Otherwise it will
426 exit with an exit code based on the node status.
432 myself = utils.HostInfo().name
433 candidates = ss.GetMasterCandidates()
434 except errors.ConfigurationError, err:
435 print "Cluster configuration incomplete: '%s'" % str(err)
436 sys.exit(constants.EXIT_NODESETUP_ERROR)
437 except errors.ResolverError, err:
438 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
439 sys.exit(constants.EXIT_NODESETUP_ERROR)
441 if myself not in candidates:
443 sys.stderr.write("Not master candidate, exiting.\n")
444 sys.exit(constants.EXIT_NOTCANDIDATE)