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
37 from ganeti import objects
40 SSCONF_LOCK_TIMEOUT = 10
42 RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$')
45 class SimpleConfigReader(object):
46 """Simple class to read configuration file.
49 def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
50 """Initializes this class.
52 @type file_name: string
53 @param file_name: Configuration file path
56 self._file_name = file_name
57 self._last_inode = None
58 self._last_mtime = None
59 self._last_size = None
60 # we need a forced reload at class init time, to initialize _last_*
61 self._Load(force=True)
63 def _Load(self, force=False):
64 """Loads (or reloads) the config file.
67 @param force: whether to force the reload without checking the mtime
69 @return: boolean value that says whether we reloaded the configuration or
70 not (because we decided it was already up-to-date)
74 cfg_stat = os.stat(self._file_name)
75 except EnvironmentError, err:
76 raise errors.ConfigurationError("Cannot stat config file %s: %s" %
77 (self._file_name, err))
78 inode = cfg_stat.st_ino
79 mtime = cfg_stat.st_mtime
80 size = cfg_stat.st_size
83 if force or inode != self._last_inode or \
84 mtime > self._last_mtime or \
85 size != self._last_size:
86 self._last_inode = inode
87 self._last_mtime = mtime
88 self._last_size = size
95 self._config_data = serializer.Load(utils.ReadFile(self._file_name))
96 except EnvironmentError, err:
97 raise errors.ConfigurationError("Cannot read config file %s: %s" %
98 (self._file_name, err))
99 except ValueError, err:
100 raise errors.ConfigurationError("Cannot load config file %s: %s" %
101 (self._file_name, err))
103 self._ip_to_instance = {}
104 self._instances_ips = []
105 self._inst_ips_by_link = {}
106 c_nparams = self._config_data['cluster']['nicparams'][constants.PP_DEFAULT]
107 for iname in self._config_data['instances']:
108 instance = self._config_data['instances'][iname]
109 for nic in instance['nics']:
110 if 'ip' in nic and nic['ip']:
111 self._instances_ips.append(nic['ip'])
112 self._ip_to_instance[nic['ip']] = iname
113 params = objects.FillDict(c_nparams, nic['nicparams'])
114 if not params['link'] in self._inst_ips_by_link:
115 self._inst_ips_by_link[params['link']] = []
116 self._inst_ips_by_link[params['link']].append(nic['ip'])
118 self._nodes_primary_ips = []
119 self._mc_primary_ips = []
120 for node_name in self._config_data["nodes"]:
121 node = self._config_data["nodes"][node_name]
122 self._nodes_primary_ips.append(node["primary_ip"])
123 if node["master_candidate"]:
124 self._mc_primary_ips.append(node["primary_ip"])
128 # Clients can request a reload of the config file, so we export our internal
129 # _Load function as Reload.
132 def GetClusterName(self):
133 return self._config_data["cluster"]["cluster_name"]
135 def GetHostKey(self):
136 return self._config_data["cluster"]["rsahostkeypub"]
138 def GetMasterNode(self):
139 return self._config_data["cluster"]["master_node"]
141 def GetMasterIP(self):
142 return self._config_data["cluster"]["master_ip"]
144 def GetMasterNetdev(self):
145 return self._config_data["cluster"]["master_netdev"]
147 def GetFileStorageDir(self):
148 return self._config_data["cluster"]["file_storage_dir"]
150 def GetNodeList(self):
151 return self._config_data["nodes"].keys()
153 def GetConfigSerialNo(self):
154 return self._config_data["serial_no"]
156 def GetClusterSerialNo(self):
157 return self._config_data["cluster"]["serial_no"]
159 def GetNodeStatusFlags(self, node):
160 """Get a node's status flags
163 @param node: node name
164 @rtype: (bool, bool, bool)
165 @return: (master_candidate, drained, offline) (or None if no such node)
168 if node not in self._config_data["nodes"]:
171 master_candidate = self._config_data["nodes"][node]["master_candidate"]
172 drained = self._config_data["nodes"][node]["drained"]
173 offline = self._config_data["nodes"][node]["offline"]
174 return master_candidate, drained, offline
176 def GetInstanceByIp(self, ip):
177 if ip not in self._ip_to_instance:
179 return self._ip_to_instance[ip]
181 def GetNodePrimaryIp(self, node):
182 """Get a node's primary ip
185 @param node: node name
186 @rtype: string, or None
187 @return: node's primary ip, or None if no such node
190 if node not in self._config_data["nodes"]:
192 return self._config_data["nodes"][node]["primary_ip"]
194 def GetInstancePrimaryNode(self, instance):
195 """Get an instance's primary node
197 @type instance: string
198 @param instance: instance name
199 @rtype: string, or None
200 @return: primary node, or None if no such instance
203 if instance not in self._config_data["instances"]:
205 return self._config_data["instances"][instance]["primary_node"]
207 def GetNodesPrimaryIps(self):
208 return self._nodes_primary_ips
210 def GetMasterCandidatesPrimaryIps(self):
211 return self._mc_primary_ips
213 def GetInstancesIps(self, link):
215 return self._instances_ips
216 if link in self._inst_ips_by_link:
217 return self._inst_ips_by_link[link]
222 class SimpleStore(object):
223 """Interface to static cluster data.
225 This is different that the config.ConfigWriter and
226 SimpleConfigReader classes in that it holds data that will always be
227 present, even on nodes which don't have all the cluster data.
229 Other particularities of the datastore:
230 - keys are restricted to predefined values
233 _SS_FILEPREFIX = "ssconf_"
235 constants.SS_CLUSTER_NAME,
236 constants.SS_CLUSTER_TAGS,
237 constants.SS_FILE_STORAGE_DIR,
238 constants.SS_MASTER_CANDIDATES,
239 constants.SS_MASTER_CANDIDATES_IPS,
240 constants.SS_MASTER_IP,
241 constants.SS_MASTER_NETDEV,
242 constants.SS_MASTER_NODE,
243 constants.SS_NODE_LIST,
244 constants.SS_NODE_PRIMARY_IPS,
245 constants.SS_NODE_SECONDARY_IPS,
246 constants.SS_OFFLINE_NODES,
247 constants.SS_ONLINE_NODES,
248 constants.SS_INSTANCE_LIST,
249 constants.SS_RELEASE_VERSION,
253 def __init__(self, cfg_location=None):
254 if cfg_location is None:
255 self._cfg_dir = constants.DATA_DIR
257 self._cfg_dir = cfg_location
259 def KeyToFilename(self, key):
260 """Convert a given key into filename.
263 if key not in self._VALID_KEYS:
264 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
267 filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
270 def _ReadFile(self, key):
271 """Generic routine to read keys.
273 This will read the file which holds the value requested. Errors
274 will be changed into ConfigurationErrors.
277 filename = self.KeyToFilename(key)
279 data = utils.ReadFile(filename, size=self._MAX_SIZE)
280 except EnvironmentError, err:
281 raise errors.ConfigurationError("Can't read from the ssconf file:"
283 data = data.rstrip('\n')
286 def WriteFiles(self, values):
287 """Writes ssconf files used by external scripts.
290 @param values: Dictionary of (name, value)
293 ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
295 # Get lock while writing files
296 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
298 for name, value in values.iteritems():
299 if value and not value.endswith("\n"):
301 utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
305 def GetFileList(self):
306 """Return the list of all config files.
308 This is used for computing node replication data.
311 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
313 def GetClusterName(self):
314 """Get the cluster name.
317 return self._ReadFile(constants.SS_CLUSTER_NAME)
319 def GetFileStorageDir(self):
320 """Get the file storage dir.
323 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
325 def GetMasterCandidates(self):
326 """Return the list of master candidates.
329 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
330 nl = data.splitlines(False)
333 def GetMasterCandidatesIPList(self):
334 """Return the list of master candidates' primary IP.
337 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
338 nl = data.splitlines(False)
341 def GetMasterIP(self):
342 """Get the IP of the master node for this cluster.
345 return self._ReadFile(constants.SS_MASTER_IP)
347 def GetMasterNetdev(self):
348 """Get the netdev to which we'll add the master ip.
351 return self._ReadFile(constants.SS_MASTER_NETDEV)
353 def GetMasterNode(self):
354 """Get the hostname of the master node for this cluster.
357 return self._ReadFile(constants.SS_MASTER_NODE)
359 def GetNodeList(self):
360 """Return the list of cluster nodes.
363 data = self._ReadFile(constants.SS_NODE_LIST)
364 nl = data.splitlines(False)
367 def GetNodePrimaryIPList(self):
368 """Return the list of cluster nodes' primary IP.
371 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
372 nl = data.splitlines(False)
375 def GetNodeSecondaryIPList(self):
376 """Return the list of cluster nodes' secondary IP.
379 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
380 nl = data.splitlines(False)
383 def GetClusterTags(self):
384 """Return the cluster tags.
387 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
388 nl = data.splitlines(False)
392 def GetMasterAndMyself(ss=None):
393 """Get the master node and my own hostname.
395 This can be either used for a 'soft' check (compared to CheckMaster,
396 which exits) or just for computing both at the same time.
398 The function does not handle any errors, these should be handled in
399 the caller (errors.ConfigurationError, errors.ResolverError).
401 @param ss: either a sstore.SimpleConfigReader or a
402 sstore.SimpleStore instance
404 @return: a tuple (master node name, my own name)
409 return ss.GetMasterNode(), utils.HostInfo().name
412 def CheckMaster(debug, ss=None):
413 """Checks the node setup.
415 If this is the master, the function will return. Otherwise it will
416 exit with an exit code based on the node status.
420 master_name, myself = GetMasterAndMyself(ss)
421 except errors.ConfigurationError, err:
422 print "Cluster configuration incomplete: '%s'" % str(err)
423 sys.exit(constants.EXIT_NODESETUP_ERROR)
424 except errors.ResolverError, err:
425 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
426 sys.exit(constants.EXIT_NODESETUP_ERROR)
428 if myself != master_name:
430 sys.stderr.write("Not master, exiting.\n")
431 sys.exit(constants.EXIT_NOTMASTER)
434 def CheckMasterCandidate(debug, ss=None):
435 """Checks the node setup.
437 If this is a master candidate, the function will return. Otherwise it will
438 exit with an exit code based on the node status.
444 myself = utils.HostInfo().name
445 candidates = ss.GetMasterCandidates()
446 except errors.ConfigurationError, err:
447 print "Cluster configuration incomplete: '%s'" % str(err)
448 sys.exit(constants.EXIT_NODESETUP_ERROR)
449 except errors.ResolverError, err:
450 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
451 sys.exit(constants.EXIT_NODESETUP_ERROR)
453 if myself not in candidates:
455 sys.stderr.write("Not master candidate, exiting.\n")
456 sys.exit(constants.EXIT_NOTCANDIDATE)