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
38 from ganeti import netutils
41 SSCONF_LOCK_TIMEOUT = 10
43 RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$')
46 class SimpleConfigReader(object):
47 """Simple class to read configuration file.
50 def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
51 """Initializes this class.
53 @type file_name: string
54 @param file_name: Configuration file path
57 self._file_name = file_name
58 self._last_inode = None
59 self._last_mtime = None
60 self._last_size = None
62 self._config_data = None
63 self._inst_ips_by_link = None
64 self._ip_to_inst_by_link = None
65 self._instances_ips = None
66 self._mc_primary_ips = None
67 self._nodes_primary_ips = None
69 # we need a forced reload at class init time, to initialize _last_*
70 self._Load(force=True)
72 def _Load(self, force=False):
73 """Loads (or reloads) the config file.
76 @param force: whether to force the reload without checking the mtime
78 @return: boolean value that says whether we reloaded the configuration or
79 not (because we decided it was already up-to-date)
83 cfg_stat = os.stat(self._file_name)
84 except EnvironmentError, err:
85 raise errors.ConfigurationError("Cannot stat config file %s: %s" %
86 (self._file_name, err))
87 inode = cfg_stat.st_ino
88 mtime = cfg_stat.st_mtime
89 size = cfg_stat.st_size
91 if (force or inode != self._last_inode or
92 mtime > self._last_mtime or
93 size != self._last_size):
94 self._last_inode = inode
95 self._last_mtime = mtime
96 self._last_size = size
102 self._config_data = serializer.Load(utils.ReadFile(self._file_name))
103 except EnvironmentError, err:
104 raise errors.ConfigurationError("Cannot read config file %s: %s" %
105 (self._file_name, err))
106 except ValueError, err:
107 raise errors.ConfigurationError("Cannot load config file %s: %s" %
108 (self._file_name, err))
110 self._ip_to_inst_by_link = {}
111 self._instances_ips = []
112 self._inst_ips_by_link = {}
113 c_nparams = self._config_data['cluster']['nicparams'][constants.PP_DEFAULT]
114 for iname in self._config_data['instances']:
115 instance = self._config_data['instances'][iname]
116 for nic in instance['nics']:
117 if 'ip' in nic and nic['ip']:
118 params = objects.FillDict(c_nparams, nic['nicparams'])
119 if not params['link'] in self._inst_ips_by_link:
120 self._inst_ips_by_link[params['link']] = []
121 self._ip_to_inst_by_link[params['link']] = {}
122 self._ip_to_inst_by_link[params['link']][nic['ip']] = iname
123 self._inst_ips_by_link[params['link']].append(nic['ip'])
125 self._nodes_primary_ips = []
126 self._mc_primary_ips = []
127 for node_name in self._config_data["nodes"]:
128 node = self._config_data["nodes"][node_name]
129 self._nodes_primary_ips.append(node["primary_ip"])
130 if node["master_candidate"]:
131 self._mc_primary_ips.append(node["primary_ip"])
135 # Clients can request a reload of the config file, so we export our internal
136 # _Load function as Reload.
139 def GetClusterName(self):
140 return self._config_data["cluster"]["cluster_name"]
142 def GetHostKey(self):
143 return self._config_data["cluster"]["rsahostkeypub"]
145 def GetMasterNode(self):
146 return self._config_data["cluster"]["master_node"]
148 def GetMasterIP(self):
149 return self._config_data["cluster"]["master_ip"]
151 def GetMasterNetdev(self):
152 return self._config_data["cluster"]["master_netdev"]
154 def GetFileStorageDir(self):
155 return self._config_data["cluster"]["file_storage_dir"]
157 def GetNodeList(self):
158 return self._config_data["nodes"].keys()
160 def GetConfigSerialNo(self):
161 return self._config_data["serial_no"]
163 def GetClusterSerialNo(self):
164 return self._config_data["cluster"]["serial_no"]
166 def GetDefaultNicParams(self):
167 return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
169 def GetDefaultNicLink(self):
170 return self.GetDefaultNicParams()[constants.NIC_LINK]
172 def GetNodeStatusFlags(self, node):
173 """Get a node's status flags
176 @param node: node name
177 @rtype: (bool, bool, bool)
178 @return: (master_candidate, drained, offline) (or None if no such node)
181 if node not in self._config_data["nodes"]:
184 master_candidate = self._config_data["nodes"][node]["master_candidate"]
185 drained = self._config_data["nodes"][node]["drained"]
186 offline = self._config_data["nodes"][node]["offline"]
187 return master_candidate, drained, offline
189 def GetInstanceByLinkIp(self, ip, link):
190 """Get instance name from its link and ip address.
193 @param ip: ip address
195 @param link: nic link
197 @return: instance name
201 link = self.GetDefaultNicLink()
202 if not link in self._ip_to_inst_by_link:
204 if not ip in self._ip_to_inst_by_link[link]:
206 return self._ip_to_inst_by_link[link][ip]
208 def GetNodePrimaryIp(self, node):
209 """Get a node's primary ip
212 @param node: node name
213 @rtype: string, or None
214 @return: node's primary ip, or None if no such node
217 if node not in self._config_data["nodes"]:
219 return self._config_data["nodes"][node]["primary_ip"]
221 def GetInstancePrimaryNode(self, instance):
222 """Get an instance's primary node
224 @type instance: string
225 @param instance: instance name
226 @rtype: string, or None
227 @return: primary node, or None if no such instance
230 if instance not in self._config_data["instances"]:
232 return self._config_data["instances"][instance]["primary_node"]
234 def GetNodesPrimaryIps(self):
235 return self._nodes_primary_ips
237 def GetMasterCandidatesPrimaryIps(self):
238 return self._mc_primary_ips
240 def GetInstancesIps(self, link):
241 """Get list of nic ips connected to a certain link.
244 @param link: nic link
246 @return: list of ips connected to that link
250 link = self.GetDefaultNicLink()
252 if link in self._inst_ips_by_link:
253 return self._inst_ips_by_link[link]
258 class SimpleStore(object):
259 """Interface to static cluster data.
261 This is different that the config.ConfigWriter and
262 SimpleConfigReader classes in that it holds data that will always be
263 present, even on nodes which don't have all the cluster data.
265 Other particularities of the datastore:
266 - keys are restricted to predefined values
269 _SS_FILEPREFIX = "ssconf_"
271 constants.SS_CLUSTER_NAME,
272 constants.SS_CLUSTER_TAGS,
273 constants.SS_FILE_STORAGE_DIR,
274 constants.SS_MASTER_CANDIDATES,
275 constants.SS_MASTER_CANDIDATES_IPS,
276 constants.SS_MASTER_IP,
277 constants.SS_MASTER_NETDEV,
278 constants.SS_MASTER_NODE,
279 constants.SS_NODE_LIST,
280 constants.SS_NODE_PRIMARY_IPS,
281 constants.SS_NODE_SECONDARY_IPS,
282 constants.SS_OFFLINE_NODES,
283 constants.SS_ONLINE_NODES,
284 constants.SS_INSTANCE_LIST,
285 constants.SS_RELEASE_VERSION,
286 constants.SS_HYPERVISOR_LIST,
287 constants.SS_MAINTAIN_NODE_HEALTH,
288 constants.SS_UID_POOL,
292 def __init__(self, cfg_location=None):
293 if cfg_location is None:
294 self._cfg_dir = constants.DATA_DIR
296 self._cfg_dir = cfg_location
298 def KeyToFilename(self, key):
299 """Convert a given key into filename.
302 if key not in self._VALID_KEYS:
303 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
306 filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
309 def _ReadFile(self, key):
310 """Generic routine to read keys.
312 This will read the file which holds the value requested. Errors
313 will be changed into ConfigurationErrors.
316 filename = self.KeyToFilename(key)
318 data = utils.ReadFile(filename, size=self._MAX_SIZE)
319 except EnvironmentError, err:
320 raise errors.ConfigurationError("Can't read from the ssconf file:"
322 data = data.rstrip('\n')
325 def WriteFiles(self, values):
326 """Writes ssconf files used by external scripts.
329 @param values: Dictionary of (name, value)
332 ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
334 # Get lock while writing files
335 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
337 for name, value in values.iteritems():
338 if value and not value.endswith("\n"):
340 if len(value) > self._MAX_SIZE:
341 raise errors.ConfigurationError("ssconf file %s above maximum size" %
343 utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
347 def GetFileList(self):
348 """Return the list of all config files.
350 This is used for computing node replication data.
353 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
355 def GetClusterName(self):
356 """Get the cluster name.
359 return self._ReadFile(constants.SS_CLUSTER_NAME)
361 def GetFileStorageDir(self):
362 """Get the file storage dir.
365 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
367 def GetMasterCandidates(self):
368 """Return the list of master candidates.
371 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
372 nl = data.splitlines(False)
375 def GetMasterCandidatesIPList(self):
376 """Return the list of master candidates' primary IP.
379 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
380 nl = data.splitlines(False)
383 def GetMasterIP(self):
384 """Get the IP of the master node for this cluster.
387 return self._ReadFile(constants.SS_MASTER_IP)
389 def GetMasterNetdev(self):
390 """Get the netdev to which we'll add the master ip.
393 return self._ReadFile(constants.SS_MASTER_NETDEV)
395 def GetMasterNode(self):
396 """Get the hostname of the master node for this cluster.
399 return self._ReadFile(constants.SS_MASTER_NODE)
401 def GetNodeList(self):
402 """Return the list of cluster nodes.
405 data = self._ReadFile(constants.SS_NODE_LIST)
406 nl = data.splitlines(False)
409 def GetNodePrimaryIPList(self):
410 """Return the list of cluster nodes' primary IP.
413 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
414 nl = data.splitlines(False)
417 def GetNodeSecondaryIPList(self):
418 """Return the list of cluster nodes' secondary IP.
421 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
422 nl = data.splitlines(False)
425 def GetClusterTags(self):
426 """Return the cluster tags.
429 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
430 nl = data.splitlines(False)
433 def GetHypervisorList(self):
434 """Return the list of enabled hypervisors.
437 data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
438 nl = data.splitlines(False)
441 def GetMaintainNodeHealth(self):
442 """Return the value of the maintain_node_health option.
445 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
446 # we rely on the bool serialization here
447 return data == "True"
449 def GetUidPool(self):
450 """Return the user-id pool definition string.
452 The separator character is a newline.
454 The return value can be parsed using uidpool.ParseUidPool()::
456 ss = ssconf.SimpleStore()
457 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
460 data = self._ReadFile(constants.SS_UID_POOL)
464 def GetMasterAndMyself(ss=None):
465 """Get the master node and my own hostname.
467 This can be either used for a 'soft' check (compared to CheckMaster,
468 which exits) or just for computing both at the same time.
470 The function does not handle any errors, these should be handled in
471 the caller (errors.ConfigurationError, errors.ResolverError).
473 @param ss: either a sstore.SimpleConfigReader or a
474 sstore.SimpleStore instance
476 @return: a tuple (master node name, my own name)
481 return ss.GetMasterNode(), netutils.HostInfo().name
484 def CheckMaster(debug, ss=None):
485 """Checks the node setup.
487 If this is the master, the function will return. Otherwise it will
488 exit with an exit code based on the node status.
492 master_name, myself = GetMasterAndMyself(ss)
493 except errors.ConfigurationError, err:
494 print "Cluster configuration incomplete: '%s'" % str(err)
495 sys.exit(constants.EXIT_NODESETUP_ERROR)
496 except errors.ResolverError, err:
497 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
498 sys.exit(constants.EXIT_NODESETUP_ERROR)
500 if myself != master_name:
502 sys.stderr.write("Not master, exiting.\n")
503 sys.exit(constants.EXIT_NOTMASTER)