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

Source Code for Module ganeti.ssconf

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008, 2010 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  import errno 
 33   
 34  from ganeti import errors 
 35  from ganeti import constants 
 36  from ganeti import utils 
 37  from ganeti import serializer 
 38  from ganeti import objects 
 39  from ganeti import netutils 
 40   
 41   
 42  SSCONF_LOCK_TIMEOUT = 10 
 43   
 44  RE_VALID_SSCONF_NAME = re.compile(r"^[-_a-z0-9]+$") 
 45   
 46   
47 -class SimpleConfigReader(object):
48 """Simple class to read configuration file. 49 50 """
51 - def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
52 """Initializes this class. 53 54 @type file_name: string 55 @param file_name: Configuration file path 56 57 """ 58 self._file_name = file_name 59 self._last_inode = None 60 self._last_mtime = None 61 self._last_size = None 62 63 self._config_data = None 64 self._inst_ips_by_link = None 65 self._ip_to_inst_by_link = None 66 self._instances_ips = None 67 self._mc_primary_ips = None 68 self._nodes_primary_ips = None 69 70 # we need a forced reload at class init time, to initialize _last_* 71 self._Load(force=True)
72
73 - def _Load(self, force=False):
74 """Loads (or reloads) the config file. 75 76 @type force: boolean 77 @param force: whether to force the reload without checking the mtime 78 @rtype: boolean 79 @return: boolean value that says whether we reloaded the configuration or 80 not (because we decided it was already up-to-date) 81 82 """ 83 try: 84 cfg_stat = os.stat(self._file_name) 85 except EnvironmentError, err: 86 raise errors.ConfigurationError("Cannot stat config file %s: %s" % 87 (self._file_name, err)) 88 inode = cfg_stat.st_ino 89 mtime = cfg_stat.st_mtime 90 size = cfg_stat.st_size 91 92 if (force or inode != self._last_inode or 93 mtime > self._last_mtime or 94 size != self._last_size): 95 self._last_inode = inode 96 self._last_mtime = mtime 97 self._last_size = size 98 else: 99 # Don't reload 100 return False 101 102 try: 103 self._config_data = serializer.Load(utils.ReadFile(self._file_name)) 104 except EnvironmentError, err: 105 raise errors.ConfigurationError("Cannot read config file %s: %s" % 106 (self._file_name, err)) 107 except ValueError, err: 108 raise errors.ConfigurationError("Cannot load config file %s: %s" % 109 (self._file_name, err)) 110 111 self._ip_to_inst_by_link = {} 112 self._instances_ips = [] 113 self._inst_ips_by_link = {} 114 c_nparams = self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT] 115 for iname in self._config_data["instances"]: 116 instance = self._config_data["instances"][iname] 117 for nic in instance["nics"]: 118 if "ip" in nic and nic["ip"]: 119 params = objects.FillDict(c_nparams, nic["nicparams"]) 120 if not params["link"] in self._inst_ips_by_link: 121 self._inst_ips_by_link[params["link"]] = [] 122 self._ip_to_inst_by_link[params["link"]] = {} 123 self._ip_to_inst_by_link[params["link"]][nic["ip"]] = iname 124 self._inst_ips_by_link[params["link"]].append(nic["ip"]) 125 126 self._nodes_primary_ips = [] 127 self._mc_primary_ips = [] 128 for node_name in self._config_data["nodes"]: 129 node = self._config_data["nodes"][node_name] 130 self._nodes_primary_ips.append(node["primary_ip"]) 131 if node["master_candidate"]: 132 self._mc_primary_ips.append(node["primary_ip"]) 133 134 return True
135 136 # Clients can request a reload of the config file, so we export our internal 137 # _Load function as Reload. 138 Reload = _Load 139
140 - def GetClusterName(self):
141 return self._config_data["cluster"]["cluster_name"]
142
143 - def GetHostKey(self):
144 return self._config_data["cluster"]["rsahostkeypub"]
145
146 - def GetMasterNode(self):
147 return self._config_data["cluster"]["master_node"]
148
149 - def GetMasterIP(self):
150 return self._config_data["cluster"]["master_ip"]
151
152 - def GetMasterNetdev(self):
153 return self._config_data["cluster"]["master_netdev"]
154
155 - def GetFileStorageDir(self):
156 return self._config_data["cluster"]["file_storage_dir"]
157
158 - def GetSharedFileStorageDir(self):
159 return self._config_data["cluster"]["shared_file_storage_dir"]
160
161 - def GetNodeList(self):
162 return self._config_data["nodes"].keys()
163
164 - def GetConfigSerialNo(self):
165 return self._config_data["serial_no"]
166
167 - def GetClusterSerialNo(self):
168 return self._config_data["cluster"]["serial_no"]
169
170 - def GetDefaultNicParams(self):
171 return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
172 175
176 - def GetNodeStatusFlags(self, node):
177 """Get a node's status flags 178 179 @type node: string 180 @param node: node name 181 @rtype: (bool, bool, bool) 182 @return: (master_candidate, drained, offline) (or None if no such node) 183 184 """ 185 if node not in self._config_data["nodes"]: 186 return None 187 188 master_candidate = self._config_data["nodes"][node]["master_candidate"] 189 drained = self._config_data["nodes"][node]["drained"] 190 offline = self._config_data["nodes"][node]["offline"] 191 return master_candidate, drained, offline
192
193 - def GetInstanceByLinkIp(self, ip, link):
194 """Get instance name from its link and ip address. 195 196 @type ip: string 197 @param ip: ip address 198 @type link: string 199 @param link: nic link 200 @rtype: string 201 @return: instance name 202 203 """ 204 if not link: 205 link = self.GetDefaultNicLink() 206 if not link in self._ip_to_inst_by_link: 207 return None 208 if not ip in self._ip_to_inst_by_link[link]: 209 return None 210 return self._ip_to_inst_by_link[link][ip]
211
212 - def GetNodePrimaryIp(self, node):
213 """Get a node's primary ip 214 215 @type node: string 216 @param node: node name 217 @rtype: string, or None 218 @return: node's primary ip, or None if no such node 219 220 """ 221 if node not in self._config_data["nodes"]: 222 return None 223 return self._config_data["nodes"][node]["primary_ip"]
224
225 - def GetInstancePrimaryNode(self, instance):
226 """Get an instance's primary node 227 228 @type instance: string 229 @param instance: instance name 230 @rtype: string, or None 231 @return: primary node, or None if no such instance 232 233 """ 234 if instance not in self._config_data["instances"]: 235 return None 236 return self._config_data["instances"][instance]["primary_node"]
237
238 - def GetNodesPrimaryIps(self):
239 return self._nodes_primary_ips
240
242 return self._mc_primary_ips
243
244 - def GetInstancesIps(self, link):
245 """Get list of nic ips connected to a certain link. 246 247 @type link: string 248 @param link: nic link 249 @rtype: list 250 @return: list of ips connected to that link 251 252 """ 253 if not link: 254 link = self.GetDefaultNicLink() 255 256 if link in self._inst_ips_by_link: 257 return self._inst_ips_by_link[link] 258 else: 259 return []
260 261
262 -class SimpleStore(object):
263 """Interface to static cluster data. 264 265 This is different that the config.ConfigWriter and 266 SimpleConfigReader classes in that it holds data that will always be 267 present, even on nodes which don't have all the cluster data. 268 269 Other particularities of the datastore: 270 - keys are restricted to predefined values 271 272 """ 273 _SS_FILEPREFIX = "ssconf_" 274 _VALID_KEYS = ( 275 constants.SS_CLUSTER_NAME, 276 constants.SS_CLUSTER_TAGS, 277 constants.SS_FILE_STORAGE_DIR, 278 constants.SS_SHARED_FILE_STORAGE_DIR, 279 constants.SS_MASTER_CANDIDATES, 280 constants.SS_MASTER_CANDIDATES_IPS, 281 constants.SS_MASTER_IP, 282 constants.SS_MASTER_NETDEV, 283 constants.SS_MASTER_NODE, 284 constants.SS_NODE_LIST, 285 constants.SS_NODE_PRIMARY_IPS, 286 constants.SS_NODE_SECONDARY_IPS, 287 constants.SS_OFFLINE_NODES, 288 constants.SS_ONLINE_NODES, 289 constants.SS_PRIMARY_IP_FAMILY, 290 constants.SS_INSTANCE_LIST, 291 constants.SS_RELEASE_VERSION, 292 constants.SS_HYPERVISOR_LIST, 293 constants.SS_MAINTAIN_NODE_HEALTH, 294 constants.SS_UID_POOL, 295 constants.SS_NODEGROUPS, 296 ) 297 _MAX_SIZE = 131072 298
299 - def __init__(self, cfg_location=None):
300 if cfg_location is None: 301 self._cfg_dir = constants.DATA_DIR 302 else: 303 self._cfg_dir = cfg_location
304
305 - def KeyToFilename(self, key):
306 """Convert a given key into filename. 307 308 """ 309 if key not in self._VALID_KEYS: 310 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" 311 % str(key)) 312 313 filename = self._cfg_dir + "/" + self._SS_FILEPREFIX + key 314 return filename
315
316 - def _ReadFile(self, key, default=None):
317 """Generic routine to read keys. 318 319 This will read the file which holds the value requested. Errors 320 will be changed into ConfigurationErrors. 321 322 """ 323 filename = self.KeyToFilename(key) 324 try: 325 data = utils.ReadFile(filename, size=self._MAX_SIZE) 326 except EnvironmentError, err: 327 if err.errno == errno.ENOENT and default is not None: 328 return default 329 raise errors.ConfigurationError("Can't read from the ssconf file:" 330 " '%s'" % str(err)) 331 data = data.rstrip("\n") 332 return data
333
334 - def WriteFiles(self, values):
335 """Writes ssconf files used by external scripts. 336 337 @type values: dict 338 @param values: Dictionary of (name, value) 339 340 """ 341 ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE) 342 343 # Get lock while writing files 344 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) 345 try: 346 for name, value in values.iteritems(): 347 if value and not value.endswith("\n"): 348 value += "\n" 349 if len(value) > self._MAX_SIZE: 350 raise errors.ConfigurationError("ssconf file %s above maximum size" % 351 name) 352 utils.WriteFile(self.KeyToFilename(name), data=value, 353 mode=constants.SS_FILE_PERMS) 354 finally: 355 ssconf_lock.Unlock()
356
357 - def GetFileList(self):
358 """Return the list of all config files. 359 360 This is used for computing node replication data. 361 362 """ 363 return [self.KeyToFilename(key) for key in self._VALID_KEYS]
364
365 - def GetClusterName(self):
366 """Get the cluster name. 367 368 """ 369 return self._ReadFile(constants.SS_CLUSTER_NAME)
370
371 - def GetFileStorageDir(self):
372 """Get the file storage dir. 373 374 """ 375 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
376
377 - def GetSharedFileStorageDir(self):
378 """Get the shared file storage dir. 379 380 """ 381 return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
382
383 - def GetMasterCandidates(self):
384 """Return the list of master candidates. 385 386 """ 387 data = self._ReadFile(constants.SS_MASTER_CANDIDATES) 388 nl = data.splitlines(False) 389 return nl
390
392 """Return the list of master candidates' primary IP. 393 394 """ 395 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS) 396 nl = data.splitlines(False) 397 return nl
398
399 - def GetMasterIP(self):
400 """Get the IP of the master node for this cluster. 401 402 """ 403 return self._ReadFile(constants.SS_MASTER_IP)
404
405 - def GetMasterNetdev(self):
406 """Get the netdev to which we'll add the master ip. 407 408 """ 409 return self._ReadFile(constants.SS_MASTER_NETDEV)
410
411 - def GetMasterNode(self):
412 """Get the hostname of the master node for this cluster. 413 414 """ 415 return self._ReadFile(constants.SS_MASTER_NODE)
416
417 - def GetNodeList(self):
418 """Return the list of cluster nodes. 419 420 """ 421 data = self._ReadFile(constants.SS_NODE_LIST) 422 nl = data.splitlines(False) 423 return nl
424
425 - def GetNodePrimaryIPList(self):
426 """Return the list of cluster nodes' primary IP. 427 428 """ 429 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS) 430 nl = data.splitlines(False) 431 return nl
432
433 - def GetNodeSecondaryIPList(self):
434 """Return the list of cluster nodes' secondary IP. 435 436 """ 437 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS) 438 nl = data.splitlines(False) 439 return nl
440
441 - def GetNodegroupList(self):
442 """Return the list of nodegroups. 443 444 """ 445 data = self._ReadFile(constants.SS_NODEGROUPS) 446 nl = data.splitlines(False) 447 return nl
448
449 - def GetClusterTags(self):
450 """Return the cluster tags. 451 452 """ 453 data = self._ReadFile(constants.SS_CLUSTER_TAGS) 454 nl = data.splitlines(False) 455 return nl
456
457 - def GetHypervisorList(self):
458 """Return the list of enabled hypervisors. 459 460 """ 461 data = self._ReadFile(constants.SS_HYPERVISOR_LIST) 462 nl = data.splitlines(False) 463 return nl
464
465 - def GetMaintainNodeHealth(self):
466 """Return the value of the maintain_node_health option. 467 468 """ 469 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH) 470 # we rely on the bool serialization here 471 return data == "True"
472
473 - def GetUidPool(self):
474 """Return the user-id pool definition string. 475 476 The separator character is a newline. 477 478 The return value can be parsed using uidpool.ParseUidPool():: 479 480 ss = ssconf.SimpleStore() 481 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") 482 483 """ 484 data = self._ReadFile(constants.SS_UID_POOL) 485 return data
486
487 - def GetPrimaryIPFamily(self):
488 """Return the cluster-wide primary address family. 489 490 """ 491 try: 492 return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY, 493 default=netutils.IP4Address.family)) 494 except (ValueError, TypeError), err: 495 raise errors.ConfigurationError("Error while trying to parse primary ip" 496 " family: %s" % err)
497 498
499 -def GetMasterAndMyself(ss=None):
500 """Get the master node and my own hostname. 501 502 This can be either used for a 'soft' check (compared to CheckMaster, 503 which exits) or just for computing both at the same time. 504 505 The function does not handle any errors, these should be handled in 506 the caller (errors.ConfigurationError, errors.ResolverError). 507 508 @param ss: either a sstore.SimpleConfigReader or a 509 sstore.SimpleStore instance 510 @rtype: tuple 511 @return: a tuple (master node name, my own name) 512 513 """ 514 if ss is None: 515 ss = SimpleStore() 516 return ss.GetMasterNode(), netutils.Hostname.GetSysName()
517 518
519 -def CheckMaster(debug, ss=None):
520 """Checks the node setup. 521 522 If this is the master, the function will return. Otherwise it will 523 exit with an exit code based on the node status. 524 525 """ 526 try: 527 master_name, myself = GetMasterAndMyself(ss) 528 except errors.ConfigurationError, err: 529 print "Cluster configuration incomplete: '%s'" % str(err) 530 sys.exit(constants.EXIT_NODESETUP_ERROR) 531 except errors.ResolverError, err: 532 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0]) 533 sys.exit(constants.EXIT_NODESETUP_ERROR) 534 535 if myself != master_name: 536 if debug: 537 sys.stderr.write("Not master, exiting.\n") 538 sys.exit(constants.EXIT_NOTMASTER)
539