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, 2011, 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30   
 31  """Global Configuration data for Ganeti. 
 32   
 33  This module provides the interface to a special case of cluster 
 34  configuration data, which is mostly static and available to all nodes. 
 35   
 36  """ 
 37   
 38  import sys 
 39  import errno 
 40  import logging 
 41   
 42  from ganeti import compat 
 43  from ganeti import errors 
 44  from ganeti import constants 
 45  from ganeti import utils 
 46  from ganeti import netutils 
 47  from ganeti import pathutils 
 48   
 49   
 50  SSCONF_LOCK_TIMEOUT = 10 
 51   
 52  #: Valid ssconf keys 
 53  _VALID_KEYS = compat.UniqueFrozenset([ 
 54    constants.SS_CLUSTER_NAME, 
 55    constants.SS_CLUSTER_TAGS, 
 56    constants.SS_FILE_STORAGE_DIR, 
 57    constants.SS_SHARED_FILE_STORAGE_DIR, 
 58    constants.SS_GLUSTER_STORAGE_DIR, 
 59    constants.SS_MASTER_CANDIDATES, 
 60    constants.SS_MASTER_CANDIDATES_IPS, 
 61    constants.SS_MASTER_CANDIDATES_CERTS, 
 62    constants.SS_MASTER_IP, 
 63    constants.SS_MASTER_NETDEV, 
 64    constants.SS_MASTER_NETMASK, 
 65    constants.SS_MASTER_NODE, 
 66    constants.SS_NODE_LIST, 
 67    constants.SS_NODE_PRIMARY_IPS, 
 68    constants.SS_NODE_SECONDARY_IPS, 
 69    constants.SS_NODE_VM_CAPABLE, 
 70    constants.SS_OFFLINE_NODES, 
 71    constants.SS_ONLINE_NODES, 
 72    constants.SS_PRIMARY_IP_FAMILY, 
 73    constants.SS_INSTANCE_LIST, 
 74    constants.SS_RELEASE_VERSION, 
 75    constants.SS_HYPERVISOR_LIST, 
 76    constants.SS_MAINTAIN_NODE_HEALTH, 
 77    constants.SS_UID_POOL, 
 78    constants.SS_NODEGROUPS, 
 79    constants.SS_NETWORKS, 
 80    constants.SS_HVPARAMS_XEN_PVM, 
 81    constants.SS_HVPARAMS_XEN_FAKE, 
 82    constants.SS_HVPARAMS_XEN_HVM, 
 83    constants.SS_HVPARAMS_XEN_KVM, 
 84    constants.SS_HVPARAMS_XEN_CHROOT, 
 85    constants.SS_HVPARAMS_XEN_LXC, 
 86    constants.SS_ENABLED_USER_SHUTDOWN, 
 87    constants.SS_SSH_PORTS, 
 88    ]) 
 89   
 90  #: Maximum size for ssconf files 
 91  _MAX_SIZE = 128 * 1024 
 92   
 93   
94 -def ReadSsconfFile(filename):
95 """Reads an ssconf file and verifies its size. 96 97 @type filename: string 98 @param filename: Path to file 99 @rtype: string 100 @return: File contents without newlines at the end 101 @raise RuntimeError: When the file size exceeds L{_MAX_SIZE} 102 103 """ 104 statcb = utils.FileStatHelper() 105 106 data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb) 107 108 if statcb.st.st_size > _MAX_SIZE: 109 msg = ("File '%s' has a size of %s bytes (up to %s allowed)" % 110 (filename, statcb.st.st_size, _MAX_SIZE)) 111 raise RuntimeError(msg) 112 113 return data.rstrip("\n")
114 115
116 -class SimpleStore(object):
117 """Interface to static cluster data. 118 119 This is different that the config.ConfigWriter and 120 SimpleConfigReader classes in that it holds data that will always be 121 present, even on nodes which don't have all the cluster data. 122 123 Other particularities of the datastore: 124 - keys are restricted to predefined values 125 126 """
127 - def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
128 if cfg_location is None: 129 self._cfg_dir = pathutils.DATA_DIR 130 else: 131 self._cfg_dir = cfg_location 132 133 self._lockfile = _lockfile
134
135 - def KeyToFilename(self, key):
136 """Convert a given key into filename. 137 138 """ 139 if key not in _VALID_KEYS: 140 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'" 141 % str(key)) 142 143 filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key 144 return filename
145
146 - def _ReadFile(self, key, default=None):
147 """Generic routine to read keys. 148 149 This will read the file which holds the value requested. Errors 150 will be changed into ConfigurationErrors. 151 152 """ 153 filename = self.KeyToFilename(key) 154 try: 155 return ReadSsconfFile(filename) 156 except EnvironmentError, err: 157 if err.errno == errno.ENOENT and default is not None: 158 return default 159 raise errors.ConfigurationError("Can't read ssconf file %s: %s" % 160 (filename, str(err)))
161
162 - def ReadAll(self):
163 """Reads all keys and returns their values. 164 165 @rtype: dict 166 @return: Dictionary, ssconf key as key, value as value 167 168 """ 169 result = [] 170 171 for key in _VALID_KEYS: 172 try: 173 value = self._ReadFile(key) 174 except errors.ConfigurationError: 175 # Ignore non-existing files 176 pass 177 else: 178 result.append((key, value)) 179 180 return dict(result)
181
182 - def WriteFiles(self, values, dry_run=False):
183 """Writes ssconf files used by external scripts. 184 185 @type values: dict 186 @param values: Dictionary of (name, value) 187 @type dry_run boolean 188 @param dry_run: Whether to perform a dry run 189 190 """ 191 ssconf_lock = utils.FileLock.Open(self._lockfile) 192 193 # Get lock while writing files 194 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT) 195 try: 196 for name, value in values.iteritems(): 197 if isinstance(value, (list, tuple)): 198 value = "\n".join(value) 199 if value and not value.endswith("\n"): 200 value += "\n" 201 202 if len(value) > _MAX_SIZE: 203 msg = ("Value '%s' has a length of %s bytes, but only up to %s are" 204 " allowed" % (name, len(value), _MAX_SIZE)) 205 raise errors.ConfigurationError(msg) 206 207 utils.WriteFile(self.KeyToFilename(name), data=value, 208 mode=constants.SS_FILE_PERMS, 209 dry_run=dry_run) 210 finally: 211 ssconf_lock.Unlock()
212
213 - def GetFileList(self):
214 """Return the list of all config files. 215 216 This is used for computing node replication data. 217 218 """ 219 return [self.KeyToFilename(key) for key in _VALID_KEYS]
220
221 - def GetClusterName(self):
222 """Get the cluster name. 223 224 """ 225 return self._ReadFile(constants.SS_CLUSTER_NAME)
226
227 - def GetFileStorageDir(self):
228 """Get the file storage dir. 229 230 """ 231 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
232
233 - def GetSharedFileStorageDir(self):
234 """Get the shared file storage dir. 235 236 """ 237 return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
238
239 - def GetGlusterStorageDir(self):
240 """Get the Gluster storage dir. 241 242 """ 243 return self._ReadFile(constants.SS_GLUSTER_STORAGE_DIR)
244
245 - def GetMasterCandidates(self):
246 """Return the list of master candidates. 247 248 """ 249 data = self._ReadFile(constants.SS_MASTER_CANDIDATES) 250 nl = data.splitlines(False) 251 return nl
252
254 """Return the list of master candidates' primary IP. 255 256 """ 257 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS) 258 nl = data.splitlines(False) 259 return nl
260
261 - def _GetDictOfSsconfMap(self, ss_file_key):
262 """Reads a file with lines like key=value and returns a dict. 263 264 This utility function reads a file containing ssconf values of 265 the form "key=value", splits the lines at "=" and returns a 266 dictionary mapping the keys to the values. 267 268 @type ss_file_key: string 269 @param ss_file_key: the constant referring to an ssconf file 270 @rtype: dict of string to string 271 @return: a dictionary mapping the keys to the values 272 273 """ 274 data = self._ReadFile(ss_file_key) 275 lines = data.splitlines(False) 276 mapping = {} 277 for line in lines: 278 (key, value) = line.split("=") 279 mapping[key] = value 280 return mapping
281
283 """Returns the map of master candidate UUIDs to ssl cert. 284 285 @rtype: dict of string to string 286 @return: dictionary mapping the master candidates' UUIDs 287 to their SSL certificate digests 288 289 """ 290 return self._GetDictOfSsconfMap(constants.SS_MASTER_CANDIDATES_CERTS)
291
292 - def GetSshPortMap(self):
293 """Returns the map of node names to SSH port. 294 295 @rtype: dict of string to string 296 @return: dictionary mapping the node names to their SSH port 297 298 """ 299 return dict([(node_name, int(ssh_port)) for 300 node_name, ssh_port in 301 self._GetDictOfSsconfMap(constants.SS_SSH_PORTS).items()])
302
303 - def GetMasterIP(self):
304 """Get the IP of the master node for this cluster. 305 306 """ 307 return self._ReadFile(constants.SS_MASTER_IP)
308
309 - def GetMasterNetdev(self):
310 """Get the netdev to which we'll add the master ip. 311 312 """ 313 return self._ReadFile(constants.SS_MASTER_NETDEV)
314
315 - def GetMasterNetmask(self):
316 """Get the master netmask. 317 318 """ 319 try: 320 return self._ReadFile(constants.SS_MASTER_NETMASK) 321 except errors.ConfigurationError: 322 family = self.GetPrimaryIPFamily() 323 ipcls = netutils.IPAddress.GetClassFromIpFamily(family) 324 return ipcls.iplen
325
326 - def GetMasterNode(self):
327 """Get the hostname of the master node for this cluster. 328 329 """ 330 return self._ReadFile(constants.SS_MASTER_NODE)
331
332 - def GetNodeList(self):
333 """Return the list of cluster nodes. 334 335 """ 336 data = self._ReadFile(constants.SS_NODE_LIST) 337 nl = data.splitlines(False) 338 return nl
339
340 - def GetOnlineNodeList(self):
341 """Return the list of online cluster nodes. 342 343 """ 344 data = self._ReadFile(constants.SS_ONLINE_NODES) 345 nl = data.splitlines(False) 346 return nl
347
348 - def GetNodePrimaryIPList(self):
349 """Return the list of cluster nodes' primary IP. 350 351 """ 352 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS) 353 nl = data.splitlines(False) 354 return nl
355
356 - def GetNodeSecondaryIPList(self):
357 """Return the list of cluster nodes' secondary IP. 358 359 """ 360 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS) 361 nl = data.splitlines(False) 362 return nl
363
364 - def GetNodesVmCapable(self):
365 """Return the cluster nodes' vm capable value. 366 367 @rtype: dict of string to bool 368 @return: mapping of node names to vm capable values 369 370 """ 371 data = self._ReadFile(constants.SS_NODE_VM_CAPABLE) 372 vm_capable = {} 373 for line in data.splitlines(False): 374 (node_uuid, node_vm_capable) = line.split("=") 375 vm_capable[node_uuid] = node_vm_capable == "True" 376 return vm_capable
377
378 - def GetNodegroupList(self):
379 """Return the list of nodegroups. 380 381 """ 382 data = self._ReadFile(constants.SS_NODEGROUPS) 383 nl = data.splitlines(False) 384 return nl
385
386 - def GetNetworkList(self):
387 """Return the list of networks. 388 389 """ 390 data = self._ReadFile(constants.SS_NETWORKS) 391 nl = data.splitlines(False) 392 return nl
393
394 - def GetClusterTags(self):
395 """Return the cluster tags. 396 397 """ 398 data = self._ReadFile(constants.SS_CLUSTER_TAGS) 399 nl = data.splitlines(False) 400 return nl
401
402 - def GetHypervisorList(self):
403 """Return the list of enabled hypervisors. 404 405 """ 406 data = self._ReadFile(constants.SS_HYPERVISOR_LIST) 407 nl = data.splitlines(False) 408 return nl
409
410 - def GetHvparamsForHypervisor(self, hvname):
411 """Return the hypervisor parameters of the given hypervisor. 412 413 @type hvname: string 414 @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES} 415 @rtype: dict of strings 416 @returns: dictionary with hypervisor parameters 417 418 """ 419 return self._GetDictOfSsconfMap(constants.SS_HVPARAMS_PREF + hvname)
420
421 - def GetHvparams(self):
422 """Return the hypervisor parameters of all hypervisors. 423 424 @rtype: dict of dict of strings 425 @returns: dictionary mapping hypervisor names to hvparams 426 427 """ 428 all_hvparams = {} 429 for hv in constants.HYPER_TYPES: 430 all_hvparams[hv] = self.GetHvparamsForHypervisor(hv) 431 return all_hvparams
432
433 - def GetMaintainNodeHealth(self):
434 """Return the value of the maintain_node_health option. 435 436 """ 437 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH) 438 # we rely on the bool serialization here 439 return data == "True"
440
441 - def GetUidPool(self):
442 """Return the user-id pool definition string. 443 444 The separator character is a newline. 445 446 The return value can be parsed using uidpool.ParseUidPool():: 447 448 ss = ssconf.SimpleStore() 449 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") 450 451 """ 452 data = self._ReadFile(constants.SS_UID_POOL) 453 return data
454
455 - def GetPrimaryIPFamily(self):
456 """Return the cluster-wide primary address family. 457 458 """ 459 try: 460 return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY, 461 default=netutils.IP4Address.family)) 462 except (ValueError, TypeError), err: 463 raise errors.ConfigurationError("Error while trying to parse primary IP" 464 " family: %s" % err)
465
466 - def GetEnabledUserShutdown(self):
467 """Return whether user shutdown is enabled. 468 469 @rtype: bool 470 @return: 'True' if user shutdown is enabled, 'False' otherwise 471 472 """ 473 return self._ReadFile(constants.SS_ENABLED_USER_SHUTDOWN) == "True"
474 475
476 -def WriteSsconfFiles(values, dry_run=False):
477 """Update all ssconf files. 478 479 Wrapper around L{SimpleStore.WriteFiles}. 480 481 """ 482 SimpleStore().WriteFiles(values, dry_run=dry_run)
483 484
485 -def GetMasterAndMyself(ss=None):
486 """Get the master node and my own hostname. 487 488 This can be either used for a 'soft' check (compared to CheckMaster, 489 which exits) or just for computing both at the same time. 490 491 The function does not handle any errors, these should be handled in 492 the caller (errors.ConfigurationError, errors.ResolverError). 493 494 @param ss: either a sstore.SimpleConfigReader or a 495 sstore.SimpleStore instance 496 @rtype: tuple 497 @return: a tuple (master node name, my own name) 498 499 """ 500 if ss is None: 501 ss = SimpleStore() 502 return ss.GetMasterNode(), netutils.Hostname.GetSysName()
503 504
505 -def CheckMaster(debug, ss=None):
506 """Checks the node setup. 507 508 If this is the master, the function will return. Otherwise it will 509 exit with an exit code based on the node status. 510 511 """ 512 try: 513 master_name, myself = GetMasterAndMyself(ss) 514 except errors.ConfigurationError, err: 515 print "Cluster configuration incomplete: '%s'" % str(err) 516 sys.exit(constants.EXIT_NODESETUP_ERROR) 517 except errors.ResolverError, err: 518 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0]) 519 sys.exit(constants.EXIT_NODESETUP_ERROR) 520 521 if myself != master_name: 522 if debug: 523 sys.stderr.write("Not master, exiting.\n") 524 sys.exit(constants.EXIT_NOTMASTER)
525 526
527 -def VerifyClusterName(name, _cfg_location=None):
528 """Verifies cluster name against a local cluster name. 529 530 @type name: string 531 @param name: Cluster name 532 533 """ 534 sstore = SimpleStore(cfg_location=_cfg_location) 535 536 try: 537 local_name = sstore.GetClusterName() 538 except errors.ConfigurationError, err: 539 logging.debug("Can't get local cluster name: %s", err) 540 else: 541 if name != local_name: 542 raise errors.GenericError("Current cluster name is '%s'" % local_name)
543 544
545 -def VerifyKeys(keys):
546 """Raises an exception if unknown ssconf keys are given. 547 548 @type keys: sequence 549 @param keys: Key names to verify 550 @raise errors.GenericError: When invalid keys were found 551 552 """ 553 invalid = frozenset(keys) - _VALID_KEYS 554 if invalid: 555 raise errors.GenericError("Invalid ssconf keys: %s" % 556 utils.CommaJoin(sorted(invalid)))
557