Package ganeti :: Module ssconf
[hide private]
[frames] | no frames]

Source Code for Module ganeti.ssconf

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008 Google Inc. 
  5  # 
  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. 
 10  # 
 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. 
 15  # 
 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 
 19  # 02110-1301, USA. 
 20   
 21   
 22  """Global Configuration data for Ganeti. 
 23   
 24  This module provides the interface to a special case of cluster 
 25  configuration data, which is mostly static and available to all nodes. 
 26   
 27  """ 
 28   
 29  import sys 
 30  import re 
 31  import os 
 32   
 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 
 39   
 40   
 41  SSCONF_LOCK_TIMEOUT = 10 
 42   
 43  RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$') 
 44   
 45   
46 -class SimpleConfigReader(object):
47 """Simple class to read configuration file. 48 49 """
50 - def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
51 """Initializes this class. 52 53 @type file_name: string 54 @param file_name: Configuration file path 55 56 """ 57 self._file_name = file_name 58 self._last_inode = None 59 self._last_mtime = None 60 self._last_size = None 61 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 68 69 # we need a forced reload at class init time, to initialize _last_* 70 self._Load(force=True)
71
72 - def _Load(self, force=False):
73 """Loads (or reloads) the config file. 74 75 @type force: boolean 76 @param force: whether to force the reload without checking the mtime 77 @rtype: boolean 78 @return: boolean value that says whether we reloaded the configuration or 79 not (because we decided it was already up-to-date) 80 81 """ 82 try: 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 90 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 97 else: 98 # Don't reload 99 return False 100 101 try: 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)) 109 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']) 124 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"]) 132 133 return True
134 135 # Clients can request a reload of the config file, so we export our internal 136 # _Load function as Reload. 137 Reload = _Load 138
139 - def GetClusterName(self):
140 return self._config_data["cluster"]["cluster_name"]
141
142 - def GetHostKey(self):
143 return self._config_data["cluster"]["rsahostkeypub"]
144
145 - def GetMasterNode(self):
146 return self._config_data["cluster"]["master_node"]
147
148 - def GetMasterIP(self):
149 return self._config_data["cluster"]["master_ip"]
150
151 - def GetMasterNetdev(self):
152 return self._config_data["cluster"]["master_netdev"]
153
154 - def GetFileStorageDir(self):
155 return self._config_data["cluster"]["file_storage_dir"]
156
157 - def GetNodeList(self):
158 return self._config_data["nodes"].keys()
159
160 - def GetConfigSerialNo(self):
161 return self._config_data["serial_no"]
162
163 - def GetClusterSerialNo(self):
164 return self._config_data["cluster"]["serial_no"]
165
166 - def GetDefaultNicParams(self):
167 return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
168 171
172 - def GetNodeStatusFlags(self, node):
173 """Get a node's status flags 174 175 @type node: string 176 @param node: node name 177 @rtype: (bool, bool, bool) 178 @return: (master_candidate, drained, offline) (or None if no such node) 179 180 """ 181 if node not in self._config_data["nodes"]: 182 return None 183 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
188
189 - def GetInstanceByLinkIp(self, ip, link):
190 """Get instance name from its link and ip address. 191 192 @type ip: string 193 @param ip: ip address 194 @type link: string 195 @param link: nic link 196 @rtype: string 197 @return: instance name 198 199 """ 200 if not link: 201 link = self.GetDefaultNicLink() 202 if not link in self._ip_to_inst_by_link: 203 return None 204 if not ip in self._ip_to_inst_by_link[link]: 205 return None 206 return self._ip_to_inst_by_link[link][ip]
207
208 - def GetNodePrimaryIp(self, node):
209 """Get a node's primary ip 210 211 @type node: string 212 @param node: node name 213 @rtype: string, or None 214 @return: node's primary ip, or None if no such node 215 216 """ 217 if node not in self._config_data["nodes"]: 218 return None 219 return self._config_data["nodes"][node]["primary_ip"]
220
221 - def GetInstancePrimaryNode(self, instance):
222 """Get an instance's primary node 223 224 @type instance: string 225 @param instance: instance name 226 @rtype: string, or None 227 @return: primary node, or None if no such instance 228 229 """ 230 if instance not in self._config_data["instances"]: 231 return None 232 return self._config_data["instances"][instance]["primary_node"]
233
234 - def GetNodesPrimaryIps(self):
235 return self._nodes_primary_ips
236
238 return self._mc_primary_ips
239
240 - def GetInstancesIps(self, link):
241 """Get list of nic ips connected to a certain link. 242 243 @type link: string 244 @param link: nic link 245 @rtype: list 246 @return: list of ips connected to that link 247 248 """ 249 if not link: 250 link = self.GetDefaultNicLink() 251 252 if link in self._inst_ips_by_link: 253 return self._inst_ips_by_link[link] 254 else: 255 return []
256 257
258 -class SimpleStore(object):
259 """Interface to static cluster data. 260 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. 264 265 Other particularities of the datastore: 266 - keys are restricted to predefined values 267 268 """ 269 _SS_FILEPREFIX = "ssconf_" 270 _VALID_KEYS = ( 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, 289 ) 290 _MAX_SIZE = 131072 291
292 - def __init__(self, cfg_location=None):
293 if cfg_location is None: 294 self._cfg_dir = constants.DATA_DIR 295 else: 296 self._cfg_dir = cfg_location
297
298 - def KeyToFilename(self, key):
299 """Convert a given key into filename. 300 301 """ 302 if key not in self._VALID_KEYS: 303 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" 304 % str(key)) 305 306 filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key 307 return filename
308
309 - def _ReadFile(self, key):
310 """Generic routine to read keys. 311 312 This will read the file which holds the value requested. Errors 313 will be changed into ConfigurationErrors. 314 315 """ 316 filename = self.KeyToFilename(key) 317 try: 318 data = utils.ReadFile(filename, size=self._MAX_SIZE) 319 except EnvironmentError, err: 320 raise errors.ConfigurationError("Can't read from the ssconf file:" 321 " '%s'" % str(err)) 322 data = data.rstrip('\n') 323 return data
324
325 - def WriteFiles(self, values):
326 """Writes ssconf files used by external scripts. 327 328 @type values: dict 329 @param values: Dictionary of (name, value) 330 331 """ 332 ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE) 333 334 # Get lock while writing files 335 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) 336 try: 337 for name, value in values.iteritems(): 338 if value and not value.endswith("\n"): 339 value += "\n" 340 if len(value) > self._MAX_SIZE: 341 raise errors.ConfigurationError("ssconf file %s above maximum size" % 342 name) 343 utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444) 344 finally: 345 ssconf_lock.Unlock()
346
347 - def GetFileList(self):
348 """Return the list of all config files. 349 350 This is used for computing node replication data. 351 352 """ 353 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
354
355 - def GetClusterName(self):
356 """Get the cluster name. 357 358 """ 359 return self._ReadFile(constants.SS_CLUSTER_NAME)
360
361 - def GetFileStorageDir(self):
362 """Get the file storage dir. 363 364 """ 365 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
366
367 - def GetMasterCandidates(self):
368 """Return the list of master candidates. 369 370 """ 371 data = self._ReadFile(constants.SS_MASTER_CANDIDATES) 372 nl = data.splitlines(False) 373 return nl
374
376 """Return the list of master candidates' primary IP. 377 378 """ 379 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS) 380 nl = data.splitlines(False) 381 return nl
382
383 - def GetMasterIP(self):
384 """Get the IP of the master node for this cluster. 385 386 """ 387 return self._ReadFile(constants.SS_MASTER_IP)
388
389 - def GetMasterNetdev(self):
390 """Get the netdev to which we'll add the master ip. 391 392 """ 393 return self._ReadFile(constants.SS_MASTER_NETDEV)
394
395 - def GetMasterNode(self):
396 """Get the hostname of the master node for this cluster. 397 398 """ 399 return self._ReadFile(constants.SS_MASTER_NODE)
400
401 - def GetNodeList(self):
402 """Return the list of cluster nodes. 403 404 """ 405 data = self._ReadFile(constants.SS_NODE_LIST) 406 nl = data.splitlines(False) 407 return nl
408
409 - def GetNodePrimaryIPList(self):
410 """Return the list of cluster nodes' primary IP. 411 412 """ 413 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS) 414 nl = data.splitlines(False) 415 return nl
416
417 - def GetNodeSecondaryIPList(self):
418 """Return the list of cluster nodes' secondary IP. 419 420 """ 421 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS) 422 nl = data.splitlines(False) 423 return nl
424
425 - def GetClusterTags(self):
426 """Return the cluster tags. 427 428 """ 429 data = self._ReadFile(constants.SS_CLUSTER_TAGS) 430 nl = data.splitlines(False) 431 return nl
432
433 - def GetHypervisorList(self):
434 """Return the list of enabled hypervisors. 435 436 """ 437 data = self._ReadFile(constants.SS_HYPERVISOR_LIST) 438 nl = data.splitlines(False) 439 return nl
440
441 - def GetMaintainNodeHealth(self):
442 """Return the value of the maintain_node_health option. 443 444 """ 445 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH) 446 # we rely on the bool serialization here 447 return data == "True"
448
449 - def GetUidPool(self):
450 """Return the user-id pool definition string. 451 452 The separator character is a newline. 453 454 The return value can be parsed using uidpool.ParseUidPool():: 455 456 ss = ssconf.SimpleStore() 457 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") 458 459 """ 460 data = self._ReadFile(constants.SS_UID_POOL) 461 return data
462 463
464 -def GetMasterAndMyself(ss=None):
465 """Get the master node and my own hostname. 466 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. 469 470 The function does not handle any errors, these should be handled in 471 the caller (errors.ConfigurationError, errors.ResolverError). 472 473 @param ss: either a sstore.SimpleConfigReader or a 474 sstore.SimpleStore instance 475 @rtype: tuple 476 @return: a tuple (master node name, my own name) 477 478 """ 479 if ss is None: 480 ss = SimpleStore() 481 return ss.GetMasterNode(), netutils.HostInfo().name
482 483
484 -def CheckMaster(debug, ss=None):
485 """Checks the node setup. 486 487 If this is the master, the function will return. Otherwise it will 488 exit with an exit code based on the node status. 489 490 """ 491 try: 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) 499 500 if myself != master_name: 501 if debug: 502 sys.stderr.write("Not master, exiting.\n") 503 sys.exit(constants.EXIT_NOTMASTER)
504