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

Source Code for Module ganeti.config

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 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  """Configuration management for Ganeti 
  32   
  33  This module provides the interface to the Ganeti cluster configuration. 
  34   
  35  The configuration data is stored on every node but is updated on the master 
  36  only. After each update, the master distributes the data to the other nodes. 
  37   
  38  Currently, the data storage format is JSON. YAML was slow and consuming too 
  39  much memory. 
  40   
  41  """ 
  42   
  43  # pylint: disable=R0904 
  44  # R0904: Too many public methods 
  45   
  46  import copy 
  47  import os 
  48  import random 
  49  import logging 
  50  import time 
  51  import threading 
  52  import itertools 
  53   
  54  from ganeti import errors 
  55  from ganeti import utils 
  56  from ganeti import constants 
  57  import ganeti.wconfd as wc 
  58  from ganeti import objects 
  59  from ganeti import serializer 
  60  from ganeti import uidpool 
  61  from ganeti import netutils 
  62  from ganeti import runtime 
  63  from ganeti import pathutils 
  64  from ganeti import network 
65 66 67 -def GetWConfdContext(ec_id, livelock):
68 """Prepare a context for communication with WConfd. 69 70 WConfd needs to know the identity of each caller to properly manage locks and 71 detect job death. This helper function prepares the identity object given a 72 job ID (optional) and a livelock file. 73 74 @type ec_id: int, or None 75 @param ec_id: the job ID or None, if the caller isn't a job 76 @type livelock: L{ganeti.utils.livelock.LiveLock} 77 @param livelock: a livelock object holding the lockfile needed for WConfd 78 @return: the WConfd context 79 80 """ 81 if ec_id is None: 82 return (threading.current_thread().getName(), 83 livelock.GetPath(), os.getpid()) 84 else: 85 return (ec_id, 86 livelock.GetPath(), os.getpid())
87
88 89 -def GetConfig(ec_id, livelock, **kwargs):
90 """A utility function for constructing instances of ConfigWriter. 91 92 It prepares a WConfd context and uses it to create a ConfigWriter instance. 93 94 @type ec_id: int, or None 95 @param ec_id: the job ID or None, if the caller isn't a job 96 @type livelock: L{ganeti.utils.livelock.LiveLock} 97 @param livelock: a livelock object holding the lockfile needed for WConfd 98 @type kwargs: dict 99 @param kwargs: Any additional arguments for the ConfigWriter constructor 100 @rtype: L{ConfigWriter} 101 @return: the ConfigWriter context 102 103 """ 104 kwargs['wconfdcontext'] = GetWConfdContext(ec_id, livelock) 105 106 # if the config is to be opened in the accept_foreign mode, we should 107 # also tell the RPC client not to check for the master node 108 accept_foreign = kwargs.get('accept_foreign', False) 109 kwargs['wconfd'] = wc.Client(allow_non_master=accept_foreign) 110 111 return ConfigWriter(**kwargs)
112
113 114 -def _ConfigSync(shared=0):
115 """Configuration synchronization decorator. 116 117 """ 118 def wrap(fn): 119 def sync_function(*args, **kwargs): 120 with args[0].GetConfigManager(shared): 121 return fn(*args, **kwargs)
122 return sync_function 123 return wrap 124 125 # job id used for resource management at config upgrade time 126 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
127 128 129 -def _ValidateConfig(data):
130 """Verifies that a configuration dict looks valid. 131 132 This only verifies the version of the configuration. 133 134 @raise errors.ConfigurationError: if the version differs from what 135 we expect 136 137 """ 138 if data['version'] != constants.CONFIG_VERSION: 139 raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, 140 data['version'])
141
142 143 -class TemporaryReservationManager(object):
144 """A temporary resource reservation manager. 145 146 This is used to reserve resources in a job, before using them, making sure 147 other jobs cannot get them in the meantime. 148 149 """
150 - def __init__(self):
151 self._ec_reserved = {}
152
153 - def Reserved(self, resource):
154 for holder_reserved in self._ec_reserved.values(): 155 if resource in holder_reserved: 156 return True 157 return False
158
159 - def Reserve(self, ec_id, resource):
160 if self.Reserved(resource): 161 raise errors.ReservationError("Duplicate reservation for resource '%s'" 162 % str(resource)) 163 if ec_id not in self._ec_reserved: 164 self._ec_reserved[ec_id] = set([resource]) 165 else: 166 self._ec_reserved[ec_id].add(resource)
167
168 - def DropECReservations(self, ec_id):
169 if ec_id in self._ec_reserved: 170 del self._ec_reserved[ec_id]
171
172 - def GetReserved(self):
173 all_reserved = set() 174 for holder_reserved in self._ec_reserved.values(): 175 all_reserved.update(holder_reserved) 176 return all_reserved
177
178 - def GetECReserved(self, ec_id):
179 """ Used when you want to retrieve all reservations for a specific 180 execution context. E.g when commiting reserved IPs for a specific 181 network. 182 183 """ 184 ec_reserved = set() 185 if ec_id in self._ec_reserved: 186 ec_reserved.update(self._ec_reserved[ec_id]) 187 return ec_reserved
188
189 - def Generate(self, existing, generate_one_fn, ec_id):
190 """Generate a new resource of this type 191 192 """ 193 assert callable(generate_one_fn) 194 195 all_elems = self.GetReserved() 196 all_elems.update(existing) 197 retries = 64 198 while retries > 0: 199 new_resource = generate_one_fn() 200 if new_resource is not None and new_resource not in all_elems: 201 break 202 else: 203 raise errors.ConfigurationError("Not able generate new resource" 204 " (last tried: %s)" % new_resource) 205 self.Reserve(ec_id, new_resource) 206 return new_resource
207
208 209 -def _MatchNameComponentIgnoreCase(short_name, names):
210 """Wrapper around L{utils.text.MatchNameComponent}. 211 212 """ 213 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
214
215 216 -def _CheckInstanceDiskIvNames(disks):
217 """Checks if instance's disks' C{iv_name} attributes are in order. 218 219 @type disks: list of L{objects.Disk} 220 @param disks: List of disks 221 @rtype: list of tuples; (int, string, string) 222 @return: List of wrongly named disks, each tuple contains disk index, 223 expected and actual name 224 225 """ 226 result = [] 227 228 for (idx, disk) in enumerate(disks): 229 exp_iv_name = "disk/%s" % idx 230 if disk.iv_name != exp_iv_name: 231 result.append((idx, exp_iv_name, disk.iv_name)) 232 233 return result
234
235 236 -class ConfigManager(object):
237 """Locks the configuration and exposes it to be read or modified. 238 239 """
240 - def __init__(self, config_writer, shared=False):
241 assert isinstance(config_writer, ConfigWriter), \ 242 "invalid argument: Not a ConfigWriter" 243 self._config_writer = config_writer 244 self._shared = shared
245
246 - def __enter__(self):
247 try: 248 self._config_writer._OpenConfig(self._shared) # pylint: disable=W0212 249 except Exception: 250 logging.debug("Opening configuration failed") 251 try: 252 self._config_writer._CloseConfig(False) # pylint: disable=W0212 253 except Exception: # pylint: disable=W0703 254 logging.debug("Closing configuration failed as well") 255 raise
256
257 - def __exit__(self, exc_type, exc_value, traceback):
258 # save the configuration, if this was a write opreration that succeeded 259 if exc_type is not None: 260 logging.debug("Configuration operation failed," 261 " the changes will not be saved") 262 # pylint: disable=W0212 263 self._config_writer._CloseConfig(not self._shared and exc_type is None) 264 return False
265
266 267 -def _UpdateIvNames(base_idx, disks):
268 """Update the C{iv_name} attribute of disks. 269 270 @type disks: list of L{objects.Disk} 271 272 """ 273 for (idx, disk) in enumerate(disks): 274 disk.iv_name = "disk/%s" % (base_idx + idx)
275
276 277 -class ConfigWriter(object):
278 """The interface to the cluster configuration. 279 280 WARNING: The class is no longer thread-safe! 281 Each thread must construct a separate instance. 282 283 @ivar _all_rms: a list of all temporary reservation managers 284 285 """
286 - def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts, 287 accept_foreign=False, wconfdcontext=None, wconfd=None):
288 self.write_count = 0 289 self._config_data = None 290 self._SetConfigData(None) 291 self._offline = offline 292 if cfg_file is None: 293 self._cfg_file = pathutils.CLUSTER_CONF_FILE 294 else: 295 self._cfg_file = cfg_file 296 self._getents = _getents 297 self._temporary_ids = TemporaryReservationManager() 298 self._all_rms = [self._temporary_ids] 299 # Note: in order to prevent errors when resolving our name later, 300 # we compute it here once and reuse it; it's 301 # better to raise an error before starting to modify the config 302 # file than after it was modified 303 self._my_hostname = netutils.Hostname.GetSysName() 304 self._cfg_id = None 305 self._wconfdcontext = wconfdcontext 306 self._wconfd = wconfd 307 self._accept_foreign = accept_foreign 308 self._lock_count = 0 309 self._lock_current_shared = None
310
311 - def _ConfigData(self):
312 return self._config_data
313
314 - def OutDate(self):
315 self._config_data = None
316
317 - def _SetConfigData(self, cfg):
318 self._config_data = cfg
319
320 - def _GetWConfdContext(self):
321 return self._wconfdcontext
322 323 # this method needs to be static, so that we can call it on the class 324 @staticmethod
325 - def IsCluster():
326 """Check if the cluster is configured. 327 328 """ 329 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
330
331 - def _UnlockedGetNdParams(self, node):
332 nodegroup = self._UnlockedGetNodeGroup(node.group) 333 return self._ConfigData().cluster.FillND(node, nodegroup)
334 335 @_ConfigSync(shared=1)
336 - def GetNdParams(self, node):
337 """Get the node params populated with cluster defaults. 338 339 @type node: L{objects.Node} 340 @param node: The node we want to know the params for 341 @return: A dict with the filled in node params 342 343 """ 344 return self._UnlockedGetNdParams(node)
345 346 @_ConfigSync(shared=1)
347 - def GetNdGroupParams(self, nodegroup):
348 """Get the node groups params populated with cluster defaults. 349 350 @type nodegroup: L{objects.NodeGroup} 351 @param nodegroup: The node group we want to know the params for 352 @return: A dict with the filled in node group params 353 354 """ 355 return self._ConfigData().cluster.FillNDGroup(nodegroup)
356 357 @_ConfigSync(shared=1)
358 - def GetInstanceDiskParams(self, instance):
359 """Get the disk params populated with inherit chain. 360 361 @type instance: L{objects.Instance} 362 @param instance: The instance we want to know the params for 363 @return: A dict with the filled in disk params 364 365 """ 366 node = self._UnlockedGetNodeInfo(instance.primary_node) 367 nodegroup = self._UnlockedGetNodeGroup(node.group) 368 return self._UnlockedGetGroupDiskParams(nodegroup)
369
370 - def _UnlockedGetInstanceDisks(self, inst_uuid):
371 """Return the disks' info for the given instance 372 373 @type inst_uuid: string 374 @param inst_uuid: The UUID of the instance we want to know the disks for 375 376 @rtype: List of L{objects.Disk} 377 @return: A list with all the disks' info 378 379 """ 380 instance = self._UnlockedGetInstanceInfo(inst_uuid) 381 if instance is None: 382 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 383 384 return [self._UnlockedGetDiskInfo(disk_uuid) 385 for disk_uuid in instance.disks]
386 387 @_ConfigSync(shared=1)
388 - def GetInstanceDisks(self, inst_uuid):
389 """Return the disks' info for the given instance 390 391 This is a simple wrapper over L{_UnlockedGetInstanceDisks}. 392 393 """ 394 return self._UnlockedGetInstanceDisks(inst_uuid)
395
396 - def _UnlockedAddDisk(self, disk):
397 """Add a disk to the config. 398 399 @type disk: L{objects.Disk} 400 @param disk: The disk object 401 402 """ 403 if not isinstance(disk, objects.Disk): 404 raise errors.ProgrammerError("Invalid type passed to _UnlockedAddDisk") 405 406 logging.info("Adding disk %s to configuration", disk.uuid) 407 408 self._CheckUniqueUUID(disk, include_temporary=False) 409 disk.serial_no = 1 410 disk.ctime = disk.mtime = time.time() 411 disk.UpgradeConfig() 412 self._ConfigData().disks[disk.uuid] = disk 413 self._ConfigData().cluster.serial_no += 1
414
415 - def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
416 """Attach a disk to an instance. 417 418 @type inst_uuid: string 419 @param inst_uuid: The UUID of the instance object 420 @type disk_uuid: string 421 @param disk_uuid: The UUID of the disk object 422 @type idx: int 423 @param idx: the index of the newly attached disk; if not 424 passed, the disk will be attached as the last one. 425 426 """ 427 instance = self._UnlockedGetInstanceInfo(inst_uuid) 428 if instance is None: 429 raise errors.ConfigurationError("Instance %s doesn't exist" 430 % inst_uuid) 431 if disk_uuid not in self._ConfigData().disks: 432 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid) 433 434 if idx is None: 435 idx = len(instance.disks) 436 else: 437 if idx < 0: 438 raise IndexError("Not accepting negative indices other than -1") 439 elif idx > len(instance.disks): 440 raise IndexError("Got disk index %s, but there are only %s" % 441 (idx, len(instance.disks))) 442 443 # Disk must not be attached anywhere else 444 for inst in self._ConfigData().instances.values(): 445 if disk_uuid in inst.disks: 446 raise errors.ReservationError("Disk %s already attached to instance %s" 447 % (disk_uuid, inst.name)) 448 449 instance.disks.insert(idx, disk_uuid) 450 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) 451 _UpdateIvNames(idx, instance_disks[idx:]) 452 instance.serial_no += 1 453 instance.mtime = time.time()
454 455 @_ConfigSync()
456 - def AddInstanceDisk(self, inst_uuid, disk, idx=None):
457 """Add a disk to the config and attach it to instance. 458 459 This is a simple wrapper over L{_UnlockedAddDisk} and 460 L{_UnlockedAttachInstanceDisk}. 461 462 """ 463 self._UnlockedAddDisk(disk) 464 self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx)
465
466 - def _UnlockedDetachInstanceDisk(self, inst_uuid, disk_uuid):
467 """Detach a disk from an instance. 468 469 @type inst_uuid: string 470 @param inst_uuid: The UUID of the instance object 471 @type disk_uuid: string 472 @param disk_uuid: The UUID of the disk object 473 474 """ 475 instance = self._UnlockedGetInstanceInfo(inst_uuid) 476 if instance is None: 477 raise errors.ConfigurationError("Instance %s doesn't exist" 478 % inst_uuid) 479 if disk_uuid not in self._ConfigData().disks: 480 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid) 481 482 # Check if disk is attached to the instance 483 if disk_uuid not in instance.disks: 484 raise errors.ProgrammerError("Disk %s is not attached to an instance" 485 % disk_uuid) 486 487 idx = instance.disks.index(disk_uuid) 488 instance.disks.remove(disk_uuid) 489 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) 490 _UpdateIvNames(idx, instance_disks[idx:]) 491 instance.serial_no += 1 492 instance.mtime = time.time()
493
494 - def _UnlockedRemoveDisk(self, disk_uuid):
495 """Remove the disk from the configuration. 496 497 @type disk_uuid: string 498 @param disk_uuid: The UUID of the disk object 499 500 """ 501 if disk_uuid not in self._ConfigData().disks: 502 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid) 503 504 # Disk must not be attached anywhere 505 for inst in self._ConfigData().instances.values(): 506 if disk_uuid in inst.disks: 507 raise errors.ReservationError("Cannot remove disk %s. Disk is" 508 " attached to instance %s" 509 % (disk_uuid, inst.name)) 510 511 # Remove disk from config file 512 del self._ConfigData().disks[disk_uuid] 513 self._ConfigData().cluster.serial_no += 1
514 515 @_ConfigSync()
516 - def RemoveInstanceDisk(self, inst_uuid, disk_uuid):
517 """Detach a disk from an instance and remove it from the config. 518 519 This is a simple wrapper over L{_UnlockedDetachInstanceDisk} and 520 L{_UnlockedRemoveDisk}. 521 522 """ 523 self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid) 524 self._UnlockedRemoveDisk(disk_uuid)
525
526 - def _UnlockedGetDiskInfo(self, disk_uuid):
527 """Returns information about a disk. 528 529 It takes the information from the configuration file. 530 531 @param disk_uuid: UUID of the disk 532 533 @rtype: L{objects.Disk} 534 @return: the disk object 535 536 """ 537 if disk_uuid not in self._ConfigData().disks: 538 return None 539 540 return self._ConfigData().disks[disk_uuid]
541 542 @_ConfigSync(shared=1)
543 - def GetDiskInfo(self, disk_uuid):
544 """Returns information about a disk. 545 546 This is a simple wrapper over L{_UnlockedGetDiskInfo}. 547 548 """ 549 return self._UnlockedGetDiskInfo(disk_uuid)
550
551 - def _AllInstanceNodes(self, inst_uuid):
552 """Compute the set of all disk-related nodes for an instance. 553 554 This abstracts away some work from '_UnlockedGetInstanceNodes' 555 and '_UnlockedGetInstanceSecondaryNodes'. 556 557 @type inst_uuid: string 558 @param inst_uuid: The UUID of the instance we want to get nodes for 559 @rtype: set of strings 560 @return: A set of names for all the nodes of the instance 561 562 """ 563 instance = self._UnlockedGetInstanceInfo(inst_uuid) 564 if instance is None: 565 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 566 567 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) 568 all_nodes = [] 569 for disk in instance_disks: 570 all_nodes.extend(disk.all_nodes) 571 return (set(all_nodes), instance)
572
573 - def _UnlockedGetInstanceNodes(self, inst_uuid):
574 """Get all disk-related nodes for an instance. 575 576 For non-DRBD, this will be empty, for DRBD it will contain both 577 the primary and the secondaries. 578 579 @type inst_uuid: string 580 @param inst_uuid: The UUID of the instance we want to get nodes for 581 @rtype: list of strings 582 @return: A list of names for all the nodes of the instance 583 584 """ 585 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid) 586 # ensure that primary node is always the first 587 all_nodes.discard(instance.primary_node) 588 return (instance.primary_node, ) + tuple(all_nodes)
589 590 @_ConfigSync(shared=1)
591 - def GetInstanceNodes(self, inst_uuid):
592 """Get all disk-related nodes for an instance. 593 594 This is just a wrapper over L{_UnlockedGetInstanceNodes} 595 596 """ 597 return self._UnlockedGetInstanceNodes(inst_uuid)
598
599 - def _UnlockedGetInstanceSecondaryNodes(self, inst_uuid):
600 """Get the list of secondary nodes. 601 602 @type inst_uuid: string 603 @param inst_uuid: The UUID of the instance we want to get nodes for 604 @rtype: list of strings 605 @return: A list of names for all the secondary nodes of the instance 606 607 """ 608 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid) 609 all_nodes.discard(instance.primary_node) 610 return tuple(all_nodes)
611 612 @_ConfigSync(shared=1)
613 - def GetInstanceSecondaryNodes(self, inst_uuid):
614 """Get the list of secondary nodes. 615 616 This is a simple wrapper over L{_UnlockedGetInstanceSecondaryNodes}. 617 618 """ 619 return self._UnlockedGetInstanceSecondaryNodes(inst_uuid)
620
621 - def _UnlockedGetInstanceLVsByNode(self, inst_uuid, lvmap=None):
622 """Provide a mapping of node to LVs a given instance owns. 623 624 @type inst_uuid: string 625 @param inst_uuid: The UUID of the instance we want to 626 compute the LVsByNode for 627 @type lvmap: dict 628 @param lvmap: Optional dictionary to receive the 629 'node' : ['lv', ...] data. 630 @rtype: dict or None 631 @return: None if lvmap arg is given, otherwise, a dictionary of 632 the form { 'node_uuid' : ['volume1', 'volume2', ...], ... }; 633 volumeN is of the form "vg_name/lv_name", compatible with 634 GetVolumeList() 635 636 """ 637 def _MapLVsByNode(lvmap, devices, node_uuid): 638 """Recursive helper function.""" 639 if not node_uuid in lvmap: 640 lvmap[node_uuid] = [] 641 642 for dev in devices: 643 if dev.dev_type == constants.DT_PLAIN: 644 lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1]) 645 646 elif dev.dev_type in constants.DTS_DRBD: 647 if dev.children: 648 _MapLVsByNode(lvmap, dev.children, dev.logical_id[0]) 649 _MapLVsByNode(lvmap, dev.children, dev.logical_id[1]) 650 651 elif dev.children: 652 _MapLVsByNode(lvmap, dev.children, node_uuid)
653 654 instance = self._UnlockedGetInstanceInfo(inst_uuid) 655 if instance is None: 656 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 657 658 if lvmap is None: 659 lvmap = {} 660 ret = lvmap 661 else: 662 ret = None 663 664 _MapLVsByNode(lvmap, 665 self._UnlockedGetInstanceDisks(instance.uuid), 666 instance.primary_node) 667 return ret
668 669 @_ConfigSync(shared=1)
670 - def GetInstanceLVsByNode(self, inst_uuid, lvmap=None):
671 """Provide a mapping of node to LVs a given instance owns. 672 673 This is a simple wrapper over L{_UnlockedGetInstanceLVsByNode} 674 675 """ 676 return self._UnlockedGetInstanceLVsByNode(inst_uuid, lvmap=lvmap)
677 678 @_ConfigSync(shared=1)
679 - def GetGroupDiskParams(self, group):
680 """Get the disk params populated with inherit chain. 681 682 @type group: L{objects.NodeGroup} 683 @param group: The group we want to know the params for 684 @return: A dict with the filled in disk params 685 686 """ 687 return self._UnlockedGetGroupDiskParams(group)
688
689 - def _UnlockedGetGroupDiskParams(self, group):
690 """Get the disk params populated with inherit chain down to node-group. 691 692 @type group: L{objects.NodeGroup} 693 @param group: The group we want to know the params for 694 @return: A dict with the filled in disk params 695 696 """ 697 data = self._ConfigData().cluster.SimpleFillDP(group.diskparams) 698 assert isinstance(data, dict), "Not a dictionary: " + str(data) 699 return data
700
701 - def GenerateMAC(self, net_uuid, _ec_id):
702 """Generate a MAC for an instance. 703 704 This should check the current instances for duplicates. 705 706 """ 707 return self._wconfd.GenerateMAC(self._GetWConfdContext(), net_uuid)
708
709 - def ReserveMAC(self, mac, _ec_id):
710 """Reserve a MAC for an instance. 711 712 This only checks instances managed by this cluster, it does not 713 check for potential collisions elsewhere. 714 715 """ 716 self._wconfd.ReserveMAC(self._GetWConfdContext(), mac)
717
718 - def _UnlockedCommitTemporaryIps(self, _ec_id):
719 """Commit all reserved IP address to their respective pools 720 721 """ 722 if self._offline: 723 raise errors.ProgrammerError("Can't call CommitTemporaryIps" 724 " in offline mode") 725 ips = self._wconfd.ListReservedIps(self._GetWConfdContext()) 726 for action, address, net_uuid in ips: 727 self._UnlockedCommitIp(action, net_uuid, address)
728
729 - def _UnlockedCommitIp(self, action, net_uuid, address):
730 """Commit a reserved IP address to an IP pool. 731 732 The IP address is taken from the network's IP pool and marked as free. 733 734 """ 735 nobj = self._UnlockedGetNetwork(net_uuid) 736 if nobj is None: 737 raise errors.ProgrammerError("Network '%s' not found" % (net_uuid, )) 738 pool = network.AddressPool(nobj) 739 if action == constants.RESERVE_ACTION: 740 pool.Reserve(address) 741 elif action == constants.RELEASE_ACTION: 742 pool.Release(address)
743
744 - def ReleaseIp(self, net_uuid, address, _ec_id):
745 """Give a specific IP address back to an IP pool. 746 747 The IP address is returned to the IP pool and marked as reserved. 748 749 """ 750 if net_uuid: 751 if self._offline: 752 raise errors.ProgrammerError("Can't call ReleaseIp in offline mode") 753 self._wconfd.ReleaseIp(self._GetWConfdContext(), net_uuid, address)
754
755 - def GenerateIp(self, net_uuid, _ec_id):
756 """Find a free IPv4 address for an instance. 757 758 """ 759 if self._offline: 760 raise errors.ProgrammerError("Can't call GenerateIp in offline mode") 761 return self._wconfd.GenerateIp(self._GetWConfdContext(), net_uuid)
762
763 - def ReserveIp(self, net_uuid, address, _ec_id, check=True):
764 """Reserve a given IPv4 address for use by an instance. 765 766 """ 767 if self._offline: 768 raise errors.ProgrammerError("Can't call ReserveIp in offline mode") 769 return self._wconfd.ReserveIp(self._GetWConfdContext(), net_uuid, address, 770 check)
771
772 - def ReserveLV(self, lv_name, _ec_id):
773 """Reserve an VG/LV pair for an instance. 774 775 @type lv_name: string 776 @param lv_name: the logical volume name to reserve 777 778 """ 779 return self._wconfd.ReserveLV(self._GetWConfdContext(), lv_name)
780
781 - def GenerateDRBDSecret(self, _ec_id):
782 """Generate a DRBD secret. 783 784 This checks the current disks for duplicates. 785 786 """ 787 return self._wconfd.GenerateDRBDSecret(self._GetWConfdContext())
788 789 # FIXME: After _AllIDs is removed, move it to config_mock.py
790 - def _AllLVs(self):
791 """Compute the list of all LVs. 792 793 """ 794 lvnames = set() 795 for instance in self._ConfigData().instances.values(): 796 node_data = self._UnlockedGetInstanceLVsByNode(instance.uuid) 797 for lv_list in node_data.values(): 798 lvnames.update(lv_list) 799 return lvnames
800
801 - def _AllNICs(self):
802 """Compute the list of all NICs. 803 804 """ 805 nics = [] 806 for instance in self._ConfigData().instances.values(): 807 nics.extend(instance.nics) 808 return nics
809
810 - def _AllIDs(self, include_temporary):
811 """Compute the list of all UUIDs and names we have. 812 813 @type include_temporary: boolean 814 @param include_temporary: whether to include the _temporary_ids set 815 @rtype: set 816 @return: a set of IDs 817 818 """ 819 existing = set() 820 if include_temporary: 821 existing.update(self._temporary_ids.GetReserved()) 822 existing.update(self._AllLVs()) 823 existing.update(self._ConfigData().instances.keys()) 824 existing.update(self._ConfigData().nodes.keys()) 825 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid]) 826 return existing
827
828 - def _GenerateUniqueID(self, ec_id):
829 """Generate an unique UUID. 830 831 This checks the current node, instances and disk names for 832 duplicates. 833 834 @rtype: string 835 @return: the unique id 836 837 """ 838 existing = self._AllIDs(include_temporary=False) 839 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
840 841 @_ConfigSync(shared=1)
842 - def GenerateUniqueID(self, ec_id):
843 """Generate an unique ID. 844 845 This is just a wrapper over the unlocked version. 846 847 @type ec_id: string 848 @param ec_id: unique id for the job to reserve the id to 849 850 """ 851 return self._GenerateUniqueID(ec_id)
852
853 - def _AllMACs(self):
854 """Return all MACs present in the config. 855 856 @rtype: list 857 @return: the list of all MACs 858 859 """ 860 result = [] 861 for instance in self._ConfigData().instances.values(): 862 for nic in instance.nics: 863 result.append(nic.mac) 864 865 return result
866
867 - def _AllDRBDSecrets(self):
868 """Return all DRBD secrets present in the config. 869 870 @rtype: list 871 @return: the list of all DRBD secrets 872 873 """ 874 def helper(disk, result): 875 """Recursively gather secrets from this disk.""" 876 if disk.dev_type == constants.DT_DRBD8: 877 result.append(disk.logical_id[5]) 878 if disk.children: 879 for child in disk.children: 880 helper(child, result)
881 882 result = [] 883 for disk in self._ConfigData().disks.values(): 884 helper(disk, result) 885 886 return result 887 888 @staticmethod
889 - def _VerifyDisks(data, result):
890 """Per-disk verification checks 891 892 Extends L{result} with diagnostic information about the disks. 893 894 @type data: see L{_ConfigData} 895 @param data: configuration data 896 897 @type result: list of strings 898 @param result: list containing diagnostic messages 899 900 """ 901 instance_disk_uuids = [d for insts in data.instances.values() 902 for d in insts.disks] 903 for disk_uuid in data.disks: 904 disk = data.disks[disk_uuid] 905 result.extend(["disk %s error: %s" % (disk.uuid, msg) 906 for msg in disk.Verify()]) 907 if disk.uuid != disk_uuid: 908 result.append("disk '%s' is indexed by wrong UUID '%s'" % 909 (disk.name, disk_uuid)) 910 if disk.uuid not in instance_disk_uuids: 911 result.append("disk '%s' is not attached to any instance" % 912 disk.uuid)
913
914 - def _UnlockedVerifyConfig(self):
915 """Verify function. 916 917 @rtype: list 918 @return: a list of error messages; a non-empty list signifies 919 configuration errors 920 921 """ 922 # pylint: disable=R0914 923 result = [] 924 seen_macs = [] 925 ports = {} 926 data = self._ConfigData() 927 cluster = data.cluster 928 929 # First call WConfd to perform its checks, if we're not offline 930 if not self._offline: 931 try: 932 self._wconfd.VerifyConfig() 933 except errors.ConfigVerifyError, err: 934 try: 935 for msg in err.args[1]: 936 result.append(msg) 937 except IndexError: 938 pass 939 940 def _helper(owner, attr, value, template): 941 try: 942 utils.ForceDictType(value, template) 943 except errors.GenericError, err: 944 result.append("%s has invalid %s: %s" % (owner, attr, err))
945 946 def _helper_nic(owner, params): 947 try: 948 objects.NIC.CheckParameterSyntax(params) 949 except errors.ConfigurationError, err: 950 result.append("%s has invalid nicparams: %s" % (owner, err)) 951 952 def _helper_ipolicy(owner, ipolicy, iscluster): 953 try: 954 objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster) 955 except errors.ConfigurationError, err: 956 result.append("%s has invalid instance policy: %s" % (owner, err)) 957 for key, value in ipolicy.items(): 958 if key == constants.ISPECS_MINMAX: 959 for k in range(len(value)): 960 _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k]) 961 elif key == constants.ISPECS_STD: 962 _helper(owner, "ipolicy/" + key, value, 963 constants.ISPECS_PARAMETER_TYPES) 964 else: 965 # FIXME: assuming list type 966 if key in constants.IPOLICY_PARAMETERS: 967 exp_type = float 968 # if the value is int, it can be converted into float 969 convertible_types = [int] 970 else: 971 exp_type = list 972 convertible_types = [] 973 # Try to convert from allowed types, if necessary. 974 if any(isinstance(value, ct) for ct in convertible_types): 975 try: 976 value = exp_type(value) 977 ipolicy[key] = value 978 except ValueError: 979 pass 980 if not isinstance(value, exp_type): 981 result.append("%s has invalid instance policy: for %s," 982 " expecting %s, got %s" % 983 (owner, key, exp_type.__name__, type(value))) 984 985 def _helper_ispecs(owner, parentkey, params): 986 for (key, value) in params.items(): 987 fullkey = "/".join([parentkey, key]) 988 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES) 989 990 # check cluster parameters 991 _helper("cluster", "beparams", cluster.SimpleFillBE({}), 992 constants.BES_PARAMETER_TYPES) 993 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}), 994 constants.NICS_PARAMETER_TYPES) 995 _helper_nic("cluster", cluster.SimpleFillNIC({})) 996 _helper("cluster", "ndparams", cluster.SimpleFillND({}), 997 constants.NDS_PARAMETER_TYPES) 998 _helper_ipolicy("cluster", cluster.ipolicy, True) 999 1000 for disk_template in cluster.diskparams: 1001 if disk_template not in constants.DTS_HAVE_ACCESS: 1002 continue 1003 1004 access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS, 1005 constants.DISK_KERNELSPACE) 1006 if access not in constants.DISK_VALID_ACCESS_MODES: 1007 result.append( 1008 "Invalid value of '%s:%s': '%s' (expected one of %s)" % ( 1009 disk_template, constants.LDP_ACCESS, access, 1010 utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES) 1011 ) 1012 ) 1013 1014 self._VerifyDisks(data, result) 1015 1016 # per-instance checks 1017 for instance_uuid in data.instances: 1018 instance = data.instances[instance_uuid] 1019 if instance.uuid != instance_uuid: 1020 result.append("instance '%s' is indexed by wrong UUID '%s'" % 1021 (instance.name, instance_uuid)) 1022 if instance.primary_node not in data.nodes: 1023 result.append("instance '%s' has invalid primary node '%s'" % 1024 (instance.name, instance.primary_node)) 1025 for snode in self._UnlockedGetInstanceSecondaryNodes(instance.uuid): 1026 if snode not in data.nodes: 1027 result.append("instance '%s' has invalid secondary node '%s'" % 1028 (instance.name, snode)) 1029 for idx, nic in enumerate(instance.nics): 1030 if nic.mac in seen_macs: 1031 result.append("instance '%s' has NIC %d mac %s duplicate" % 1032 (instance.name, idx, nic.mac)) 1033 else: 1034 seen_macs.append(nic.mac) 1035 if nic.nicparams: 1036 filled = cluster.SimpleFillNIC(nic.nicparams) 1037 owner = "instance %s nic %d" % (instance.name, idx) 1038 _helper(owner, "nicparams", 1039 filled, constants.NICS_PARAMETER_TYPES) 1040 _helper_nic(owner, filled) 1041 1042 # disk template checks 1043 if not instance.disk_template in data.cluster.enabled_disk_templates: 1044 result.append("instance '%s' uses the disabled disk template '%s'." % 1045 (instance.name, instance.disk_template)) 1046 1047 # parameter checks 1048 if instance.beparams: 1049 _helper("instance %s" % instance.name, "beparams", 1050 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES) 1051 1052 # check that disks exists 1053 for disk_uuid in instance.disks: 1054 if disk_uuid not in data.disks: 1055 result.append("Instance '%s' has invalid disk '%s'" % 1056 (instance.name, disk_uuid)) 1057 1058 instance_disks = self._UnlockedGetInstanceDisks(instance.uuid) 1059 # gather the drbd ports for duplicate checks 1060 for (idx, dsk) in enumerate(instance_disks): 1061 if dsk.dev_type in constants.DTS_DRBD: 1062 tcp_port = dsk.logical_id[2] 1063 if tcp_port not in ports: 1064 ports[tcp_port] = [] 1065 ports[tcp_port].append((instance.name, "drbd disk %s" % idx)) 1066 # gather network port reservation 1067 net_port = getattr(instance, "network_port", None) 1068 if net_port is not None: 1069 if net_port not in ports: 1070 ports[net_port] = [] 1071 ports[net_port].append((instance.name, "network port")) 1072 1073 wrong_names = _CheckInstanceDiskIvNames(instance_disks) 1074 if wrong_names: 1075 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" % 1076 (idx, exp_name, actual_name)) 1077 for (idx, exp_name, actual_name) in wrong_names) 1078 1079 result.append("Instance '%s' has wrongly named disks: %s" % 1080 (instance.name, tmp)) 1081 1082 # cluster-wide pool of free ports 1083 for free_port in cluster.tcpudp_port_pool: 1084 if free_port not in ports: 1085 ports[free_port] = [] 1086 ports[free_port].append(("cluster", "port marked as free")) 1087 1088 # compute tcp/udp duplicate ports 1089 keys = ports.keys() 1090 keys.sort() 1091 for pnum in keys: 1092 pdata = ports[pnum] 1093 if len(pdata) > 1: 1094 txt = utils.CommaJoin(["%s/%s" % val for val in pdata]) 1095 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt)) 1096 1097 # highest used tcp port check 1098 if keys: 1099 if keys[-1] > cluster.highest_used_port: 1100 result.append("Highest used port mismatch, saved %s, computed %s" % 1101 (cluster.highest_used_port, keys[-1])) 1102 1103 if not data.nodes[cluster.master_node].master_candidate: 1104 result.append("Master node is not a master candidate") 1105 1106 # master candidate checks 1107 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats() 1108 if mc_now < mc_max: 1109 result.append("Not enough master candidates: actual %d, target %d" % 1110 (mc_now, mc_max)) 1111 1112 # node checks 1113 for node_uuid, node in data.nodes.items(): 1114 if node.uuid != node_uuid: 1115 result.append("Node '%s' is indexed by wrong UUID '%s'" % 1116 (node.name, node_uuid)) 1117 if [node.master_candidate, node.drained, node.offline].count(True) > 1: 1118 result.append("Node %s state is invalid: master_candidate=%s," 1119 " drain=%s, offline=%s" % 1120 (node.name, node.master_candidate, node.drained, 1121 node.offline)) 1122 if node.group not in data.nodegroups: 1123 result.append("Node '%s' has invalid group '%s'" % 1124 (node.name, node.group)) 1125 else: 1126 _helper("node %s" % node.name, "ndparams", 1127 cluster.FillND(node, data.nodegroups[node.group]), 1128 constants.NDS_PARAMETER_TYPES) 1129 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams) 1130 if used_globals: 1131 result.append("Node '%s' has some global parameters set: %s" % 1132 (node.name, utils.CommaJoin(used_globals))) 1133 1134 # nodegroups checks 1135 nodegroups_names = set() 1136 for nodegroup_uuid in data.nodegroups: 1137 nodegroup = data.nodegroups[nodegroup_uuid] 1138 if nodegroup.uuid != nodegroup_uuid: 1139 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'" 1140 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid)) 1141 if utils.UUID_RE.match(nodegroup.name.lower()): 1142 result.append("node group '%s' (uuid: '%s') has uuid-like name" % 1143 (nodegroup.name, nodegroup.uuid)) 1144 if nodegroup.name in nodegroups_names: 1145 result.append("duplicate node group name '%s'" % nodegroup.name) 1146 else: 1147 nodegroups_names.add(nodegroup.name) 1148 group_name = "group %s" % nodegroup.name 1149 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy), 1150 False) 1151 if nodegroup.ndparams: 1152 _helper(group_name, "ndparams", 1153 cluster.SimpleFillND(nodegroup.ndparams), 1154 constants.NDS_PARAMETER_TYPES) 1155 1156 # drbd minors check 1157 # FIXME: The check for DRBD map needs to be implemented in WConfd 1158 1159 # IP checks 1160 default_nicparams = cluster.nicparams[constants.PP_DEFAULT] 1161 ips = {} 1162 1163 def _AddIpAddress(ip, name): 1164 ips.setdefault(ip, []).append(name) 1165 1166 _AddIpAddress(cluster.master_ip, "cluster_ip") 1167 1168 for node in data.nodes.values(): 1169 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name) 1170 if node.secondary_ip != node.primary_ip: 1171 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name) 1172 1173 for instance in data.instances.values(): 1174 for idx, nic in enumerate(instance.nics): 1175 if nic.ip is None: 1176 continue 1177 1178 nicparams = objects.FillDict(default_nicparams, nic.nicparams) 1179 nic_mode = nicparams[constants.NIC_MODE] 1180 nic_link = nicparams[constants.NIC_LINK] 1181 1182 if nic_mode == constants.NIC_MODE_BRIDGED: 1183 link = "bridge:%s" % nic_link 1184 elif nic_mode == constants.NIC_MODE_ROUTED: 1185 link = "route:%s" % nic_link 1186 elif nic_mode == constants.NIC_MODE_OVS: 1187 link = "ovs:%s" % nic_link 1188 else: 1189 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode) 1190 1191 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network), 1192 "instance:%s/nic:%d" % (instance.name, idx)) 1193 1194 for ip, owners in ips.items(): 1195 if len(owners) > 1: 1196 result.append("IP address %s is used by multiple owners: %s" % 1197 (ip, utils.CommaJoin(owners))) 1198 1199 return result 1200
1201 - def _UnlockedVerifyConfigAndLog(self, feedback_fn=None):
1202 """Verify the configuration and log any errors. 1203 1204 The errors get logged as critical errors and also to the feedback function, 1205 if given. 1206 1207 @param feedback_fn: Callable feedback function 1208 @rtype: list 1209 @return: a list of error messages; a non-empty list signifies 1210 configuration errors 1211 1212 """ 1213 assert feedback_fn is None or callable(feedback_fn) 1214 1215 # Warn on config errors, but don't abort the save - the 1216 # configuration has already been modified, and we can't revert; 1217 # the best we can do is to warn the user and save as is, leaving 1218 # recovery to the user 1219 config_errors = self._UnlockedVerifyConfig() 1220 if config_errors: 1221 errmsg = ("Configuration data is not consistent: %s" % 1222 (utils.CommaJoin(config_errors))) 1223 logging.critical(errmsg) 1224 if feedback_fn: 1225 feedback_fn(errmsg) 1226 return config_errors
1227 1228 @_ConfigSync(shared=1)
1229 - def VerifyConfig(self):
1230 """Verify function. 1231 1232 This is just a wrapper over L{_UnlockedVerifyConfig}. 1233 1234 @rtype: list 1235 @return: a list of error messages; a non-empty list signifies 1236 configuration errors 1237 1238 """ 1239 return self._UnlockedVerifyConfig()
1240 1241 @_ConfigSync()
1242 - def AddTcpUdpPort(self, port):
1243 """Adds a new port to the available port pool. 1244 1245 @warning: this method does not "flush" the configuration (via 1246 L{_WriteConfig}); callers should do that themselves once the 1247 configuration is stable 1248 1249 """ 1250 if not isinstance(port, int): 1251 raise errors.ProgrammerError("Invalid type passed for port") 1252 1253 self._ConfigData().cluster.tcpudp_port_pool.add(port)
1254 1255 @_ConfigSync(shared=1)
1256 - def GetPortList(self):
1257 """Returns a copy of the current port list. 1258 1259 """ 1260 return self._ConfigData().cluster.tcpudp_port_pool.copy()
1261 1262 @_ConfigSync()
1263 - def AllocatePort(self):
1264 """Allocate a port. 1265 1266 The port will be taken from the available port pool or from the 1267 default port range (and in this case we increase 1268 highest_used_port). 1269 1270 """ 1271 # If there are TCP/IP ports configured, we use them first. 1272 if self._ConfigData().cluster.tcpudp_port_pool: 1273 port = self._ConfigData().cluster.tcpudp_port_pool.pop() 1274 else: 1275 port = self._ConfigData().cluster.highest_used_port + 1 1276 if port >= constants.LAST_DRBD_PORT: 1277 raise errors.ConfigurationError("The highest used port is greater" 1278 " than %s. Aborting." % 1279 constants.LAST_DRBD_PORT) 1280 self._ConfigData().cluster.highest_used_port = port 1281 return port
1282 1283 @_ConfigSync(shared=1)
1284 - def ComputeDRBDMap(self):
1285 """Compute the used DRBD minor/nodes. 1286 1287 This is just a wrapper over a call to WConfd. 1288 1289 @return: dictionary of node_uuid: dict of minor: instance_uuid; 1290 the returned dict will have all the nodes in it (even if with 1291 an empty list). 1292 1293 """ 1294 if self._offline: 1295 raise errors.ProgrammerError("Can't call ComputeDRBDMap in offline mode") 1296 else: 1297 return dict(map(lambda (k, v): (k, dict(v)), 1298 self._wconfd.ComputeDRBDMap()))
1299
1300 - def AllocateDRBDMinor(self, node_uuids, inst_uuid):
1301 """Allocate a drbd minor. 1302 1303 This is just a wrapper over a call to WConfd. 1304 1305 The free minor will be automatically computed from the existing 1306 devices. A node can be given multiple times in order to allocate 1307 multiple minors. The result is the list of minors, in the same 1308 order as the passed nodes. 1309 1310 @type inst_uuid: string 1311 @param inst_uuid: the instance for which we allocate minors 1312 1313 """ 1314 assert isinstance(inst_uuid, basestring), \ 1315 "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid 1316 1317 if self._offline: 1318 raise errors.ProgrammerError("Can't call AllocateDRBDMinor" 1319 " in offline mode") 1320 1321 result = self._wconfd.AllocateDRBDMinor(inst_uuid, node_uuids) 1322 logging.debug("Request to allocate drbd minors, input: %s, returning %s", 1323 node_uuids, result) 1324 return result
1325
1326 - def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1327 """Release temporary drbd minors allocated for a given instance. 1328 1329 This is just a wrapper over a call to WConfd. 1330 1331 @type inst_uuid: string 1332 @param inst_uuid: the instance for which temporary minors should be 1333 released 1334 1335 """ 1336 assert isinstance(inst_uuid, basestring), \ 1337 "Invalid argument passed to ReleaseDRBDMinors" 1338 # in offline mode we allow the calls to release DRBD minors, 1339 # because then nothing can be allocated anyway; 1340 # this is useful for testing 1341 if not self._offline: 1342 self._wconfd.ReleaseDRBDMinors(inst_uuid)
1343 1344 @_ConfigSync()
1345 - def ReleaseDRBDMinors(self, inst_uuid):
1346 """Release temporary drbd minors allocated for a given instance. 1347 1348 This should be called on the error paths, on the success paths 1349 it's automatically called by the ConfigWriter add and update 1350 functions. 1351 1352 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}. 1353 1354 @type inst_uuid: string 1355 @param inst_uuid: the instance for which temporary minors should be 1356 released 1357 1358 """ 1359 self._UnlockedReleaseDRBDMinors(inst_uuid)
1360 1361 @_ConfigSync(shared=1)
1362 - def GetConfigVersion(self):
1363 """Get the configuration version. 1364 1365 @return: Config version 1366 1367 """ 1368 return self._ConfigData().version
1369 1370 @_ConfigSync(shared=1)
1371 - def GetClusterName(self):
1372 """Get cluster name. 1373 1374 @return: Cluster name 1375 1376 """ 1377 return self._ConfigData().cluster.cluster_name
1378 1379 @_ConfigSync(shared=1)
1380 - def GetMasterNode(self):
1381 """Get the UUID of the master node for this cluster. 1382 1383 @return: Master node UUID 1384 1385 """ 1386 return self._ConfigData().cluster.master_node
1387 1388 @_ConfigSync(shared=1)
1389 - def GetMasterNodeName(self):
1390 """Get the hostname of the master node for this cluster. 1391 1392 @return: Master node hostname 1393 1394 """ 1395 return self._UnlockedGetNodeName(self._ConfigData().cluster.master_node)
1396 1397 @_ConfigSync(shared=1)
1398 - def GetMasterNodeInfo(self):
1399 """Get the master node information for this cluster. 1400 1401 @rtype: objects.Node 1402 @return: Master node L{objects.Node} object 1403 1404 """ 1405 return self._UnlockedGetNodeInfo(self._ConfigData().cluster.master_node)
1406 1407 @_ConfigSync(shared=1)
1408 - def GetMasterIP(self):
1409 """Get the IP of the master node for this cluster. 1410 1411 @return: Master IP 1412 1413 """ 1414 return self._ConfigData().cluster.master_ip
1415 1416 @_ConfigSync(shared=1)
1417 - def GetMasterNetdev(self):
1418 """Get the master network device for this cluster. 1419 1420 """ 1421 return self._ConfigData().cluster.master_netdev
1422 1423 @_ConfigSync(shared=1)
1424 - def GetMasterNetmask(self):
1425 """Get the netmask of the master node for this cluster. 1426 1427 """ 1428 return self._ConfigData().cluster.master_netmask
1429 1430 @_ConfigSync(shared=1)
1431 - def GetUseExternalMipScript(self):
1432 """Get flag representing whether to use the external master IP setup script. 1433 1434 """ 1435 return self._ConfigData().cluster.use_external_mip_script
1436 1437 @_ConfigSync(shared=1)
1438 - def GetFileStorageDir(self):
1439 """Get the file storage dir for this cluster. 1440 1441 """ 1442 return self._ConfigData().cluster.file_storage_dir
1443 1444 @_ConfigSync(shared=1)
1445 - def GetSharedFileStorageDir(self):
1446 """Get the shared file storage dir for this cluster. 1447 1448 """ 1449 return self._ConfigData().cluster.shared_file_storage_dir
1450 1451 @_ConfigSync(shared=1)
1452 - def GetGlusterStorageDir(self):
1453 """Get the Gluster storage dir for this cluster. 1454 1455 """ 1456 return self._ConfigData().cluster.gluster_storage_dir
1457 1458 @_ConfigSync(shared=1)
1459 - def GetHypervisorType(self):
1460 """Get the hypervisor type for this cluster. 1461 1462 """ 1463 return self._ConfigData().cluster.enabled_hypervisors[0]
1464 1465 @_ConfigSync(shared=1)
1466 - def GetRsaHostKey(self):
1467 """Return the rsa hostkey from the config. 1468 1469 @rtype: string 1470 @return: the rsa hostkey 1471 1472 """ 1473 return self._ConfigData().cluster.rsahostkeypub
1474 1475 @_ConfigSync(shared=1)
1476 - def GetDsaHostKey(self):
1477 """Return the dsa hostkey from the config. 1478 1479 @rtype: string 1480 @return: the dsa hostkey 1481 1482 """ 1483 return self._ConfigData().cluster.dsahostkeypub
1484 1485 @_ConfigSync(shared=1)
1486 - def GetDefaultIAllocator(self):
1487 """Get the default instance allocator for this cluster. 1488 1489 """ 1490 return self._ConfigData().cluster.default_iallocator
1491 1492 @_ConfigSync(shared=1)
1493 - def GetDefaultIAllocatorParameters(self):
1494 """Get the default instance allocator parameters for this cluster. 1495 1496 @rtype: dict 1497 @return: dict of iallocator parameters 1498 1499 """ 1500 return self._ConfigData().cluster.default_iallocator_params
1501 1502 @_ConfigSync(shared=1)
1503 - def GetPrimaryIPFamily(self):
1504 """Get cluster primary ip family. 1505 1506 @return: primary ip family 1507 1508 """ 1509 return self._ConfigData().cluster.primary_ip_family
1510 1511 @_ConfigSync(shared=1)
1512 - def GetMasterNetworkParameters(self):
1513 """Get network parameters of the master node. 1514 1515 @rtype: L{object.MasterNetworkParameters} 1516 @return: network parameters of the master node 1517 1518 """ 1519 cluster = self._ConfigData().cluster 1520 result = objects.MasterNetworkParameters( 1521 uuid=cluster.master_node, ip=cluster.master_ip, 1522 netmask=cluster.master_netmask, netdev=cluster.master_netdev, 1523 ip_family=cluster.primary_ip_family) 1524 1525 return result
1526 1527 @_ConfigSync(shared=1)
1528 - def GetInstallImage(self):
1529 """Get the install image location 1530 1531 @rtype: string 1532 @return: location of the install image 1533 1534 """ 1535 return self._ConfigData().cluster.install_image
1536 1537 @_ConfigSync()
1538 - def SetInstallImage(self, install_image):
1539 """Set the install image location 1540 1541 @type install_image: string 1542 @param install_image: location of the install image 1543 1544 """ 1545 self._ConfigData().cluster.install_image = install_image
1546 1547 @_ConfigSync(shared=1)
1548 - def GetInstanceCommunicationNetwork(self):
1549 """Get cluster instance communication network 1550 1551 @rtype: string 1552 @return: instance communication network, which is the name of the 1553 network used for instance communication 1554 1555 """ 1556 return self._ConfigData().cluster.instance_communication_network
1557 1558 @_ConfigSync()
1559 - def SetInstanceCommunicationNetwork(self, network_name):
1560 """Set cluster instance communication network 1561 1562 @type network_name: string 1563 @param network_name: instance communication network, which is the name of 1564 the network used for instance communication 1565 1566 """ 1567 self._ConfigData().cluster.instance_communication_network = network_name
1568 1569 @_ConfigSync(shared=1)
1570 - def GetZeroingImage(self):
1571 """Get the zeroing image location 1572 1573 @rtype: string 1574 @return: the location of the zeroing image 1575 1576 """ 1577 return self._config_data.cluster.zeroing_image
1578 1579 @_ConfigSync(shared=1)
1580 - def GetCompressionTools(self):
1581 """Get cluster compression tools 1582 1583 @rtype: list of string 1584 @return: a list of tools that are cleared for use in this cluster for the 1585 purpose of compressing data 1586 1587 """ 1588 return self._ConfigData().cluster.compression_tools
1589 1590 @_ConfigSync()
1591 - def SetCompressionTools(self, tools):
1592 """Set cluster compression tools 1593 1594 @type tools: list of string 1595 @param tools: a list of tools that are cleared for use in this cluster for 1596 the purpose of compressing data 1597 1598 """ 1599 self._ConfigData().cluster.compression_tools = tools
1600 1601 @_ConfigSync()
1602 - def AddNodeGroup(self, group, ec_id, check_uuid=True):
1603 """Add a node group to the configuration. 1604 1605 This method calls group.UpgradeConfig() to fill any missing attributes 1606 according to their default values. 1607 1608 @type group: L{objects.NodeGroup} 1609 @param group: the NodeGroup object to add 1610 @type ec_id: string 1611 @param ec_id: unique id for the job to use when creating a missing UUID 1612 @type check_uuid: bool 1613 @param check_uuid: add an UUID to the group if it doesn't have one or, if 1614 it does, ensure that it does not exist in the 1615 configuration already 1616 1617 """ 1618 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1619
1620 - def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1621 """Add a node group to the configuration. 1622 1623 """ 1624 logging.info("Adding node group %s to configuration", group.name) 1625 1626 # Some code might need to add a node group with a pre-populated UUID 1627 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass 1628 # the "does this UUID" exist already check. 1629 if check_uuid: 1630 self._EnsureUUID(group, ec_id) 1631 1632 try: 1633 existing_uuid = self._UnlockedLookupNodeGroup(group.name) 1634 except errors.OpPrereqError: 1635 pass 1636 else: 1637 raise errors.OpPrereqError("Desired group name '%s' already exists as a" 1638 " node group (UUID: %s)" % 1639 (group.name, existing_uuid), 1640 errors.ECODE_EXISTS) 1641 1642 group.serial_no = 1 1643 group.ctime = group.mtime = time.time() 1644 group.UpgradeConfig() 1645 1646 self._ConfigData().nodegroups[group.uuid] = group 1647 self._ConfigData().cluster.serial_no += 1
1648 1649 @_ConfigSync()
1650 - def RemoveNodeGroup(self, group_uuid):
1651 """Remove a node group from the configuration. 1652 1653 @type group_uuid: string 1654 @param group_uuid: the UUID of the node group to remove 1655 1656 """ 1657 logging.info("Removing node group %s from configuration", group_uuid) 1658 1659 if group_uuid not in self._ConfigData().nodegroups: 1660 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid) 1661 1662 assert len(self._ConfigData().nodegroups) != 1, \ 1663 "Group '%s' is the only group, cannot be removed" % group_uuid 1664 1665 del self._ConfigData().nodegroups[group_uuid] 1666 self._ConfigData().cluster.serial_no += 1
1667
1668 - def _UnlockedLookupNodeGroup(self, target):
1669 """Lookup a node group's UUID. 1670 1671 @type target: string or None 1672 @param target: group name or UUID or None to look for the default 1673 @rtype: string 1674 @return: nodegroup UUID 1675 @raises errors.OpPrereqError: when the target group cannot be found 1676 1677 """ 1678 if target is None: 1679 if len(self._ConfigData().nodegroups) != 1: 1680 raise errors.OpPrereqError("More than one node group exists. Target" 1681 " group must be specified explicitly.") 1682 else: 1683 return self._ConfigData().nodegroups.keys()[0] 1684 if target in self._ConfigData().nodegroups: 1685 return target 1686 for nodegroup in self._ConfigData().nodegroups.values(): 1687 if nodegroup.name == target: 1688 return nodegroup.uuid 1689 raise errors.OpPrereqError("Node group '%s' not found" % target, 1690 errors.ECODE_NOENT)
1691 1692 @_ConfigSync(shared=1)
1693 - def LookupNodeGroup(self, target):
1694 """Lookup a node group's UUID. 1695 1696 This function is just a wrapper over L{_UnlockedLookupNodeGroup}. 1697 1698 @type target: string or None 1699 @param target: group name or UUID or None to look for the default 1700 @rtype: string 1701 @return: nodegroup UUID 1702 1703 """ 1704 return self._UnlockedLookupNodeGroup(target)
1705
1706 - def _UnlockedGetNodeGroup(self, uuid):
1707 """Lookup a node group. 1708 1709 @type uuid: string 1710 @param uuid: group UUID 1711 @rtype: L{objects.NodeGroup} or None 1712 @return: nodegroup object, or None if not found 1713 1714 """ 1715 if uuid not in self._ConfigData().nodegroups: 1716 return None 1717 1718 return self._ConfigData().nodegroups[uuid]
1719 1720 @_ConfigSync(shared=1)
1721 - def GetNodeGroup(self, uuid):
1722 """Lookup a node group. 1723 1724 @type uuid: string 1725 @param uuid: group UUID 1726 @rtype: L{objects.NodeGroup} or None 1727 @return: nodegroup object, or None if not found 1728 1729 """ 1730 return self._UnlockedGetNodeGroup(uuid)
1731
1732 - def _UnlockedGetAllNodeGroupsInfo(self):
1733 """Get the configuration of all node groups. 1734 1735 """ 1736 return dict(self._ConfigData().nodegroups)
1737 1738 @_ConfigSync(shared=1)
1739 - def GetAllNodeGroupsInfo(self):
1740 """Get the configuration of all node groups. 1741 1742 """ 1743 return self._UnlockedGetAllNodeGroupsInfo()
1744 1745 @_ConfigSync(shared=1)
1746 - def GetAllNodeGroupsInfoDict(self):
1747 """Get the configuration of all node groups expressed as a dictionary of 1748 dictionaries. 1749 1750 """ 1751 return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()), 1752 self._UnlockedGetAllNodeGroupsInfo().items()))
1753 1754 @_ConfigSync(shared=1)
1755 - def GetNodeGroupList(self):
1756 """Get a list of node groups. 1757 1758 """ 1759 return self._ConfigData().nodegroups.keys()
1760 1761 @_ConfigSync(shared=1)
1762 - def GetNodeGroupMembersByNodes(self, nodes):
1763 """Get nodes which are member in the same nodegroups as the given nodes. 1764 1765 """ 1766 ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group 1767 return frozenset(member_uuid 1768 for node_uuid in nodes 1769 for member_uuid in 1770 self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1771 1772 @_ConfigSync(shared=1)
1773 - def GetMultiNodeGroupInfo(self, group_uuids):
1774 """Get the configuration of multiple node groups. 1775 1776 @param group_uuids: List of node group UUIDs 1777 @rtype: list 1778 @return: List of tuples of (group_uuid, group_info) 1779 1780 """ 1781 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1782 1783 @_ConfigSync()
1784 - def AddInstance(self, instance, ec_id):
1785 """Add an instance to the config. 1786 1787 This should be used after creating a new instance. 1788 1789 @type instance: L{objects.Instance} 1790 @param instance: the instance object 1791 1792 """ 1793 if not isinstance(instance, objects.Instance): 1794 raise errors.ProgrammerError("Invalid type passed to AddInstance") 1795 1796 all_macs = self._AllMACs() 1797 for nic in instance.nics: 1798 if nic.mac in all_macs: 1799 raise errors.ConfigurationError("Cannot add instance %s:" 1800 " MAC address '%s' already in use." % 1801 (instance.name, nic.mac)) 1802 1803 self._CheckUniqueUUID(instance, include_temporary=False) 1804 1805 instance.serial_no = 1 1806 instance.ctime = instance.mtime = time.time() 1807 self._ConfigData().instances[instance.uuid] = instance 1808 self._ConfigData().cluster.serial_no += 1 1809 self._UnlockedReleaseDRBDMinors(instance.uuid) 1810 # FIXME: After RemoveInstance is moved to WConfd, use its internal 1811 # function from TempRes module instead. 1812 self._UnlockedCommitTemporaryIps(ec_id)
1813
1814 - def _EnsureUUID(self, item, ec_id):
1815 """Ensures a given object has a valid UUID. 1816 1817 @param item: the instance or node to be checked 1818 @param ec_id: the execution context id for the uuid reservation 1819 1820 """ 1821 if not item.uuid: 1822 item.uuid = self._GenerateUniqueID(ec_id) 1823 else: 1824 self._CheckUniqueUUID(item, include_temporary=True)
1825
1826 - def _CheckUniqueUUID(self, item, include_temporary):
1827 """Checks that the UUID of the given object is unique. 1828 1829 @param item: the instance or node to be checked 1830 @param include_temporary: whether temporarily generated UUID's should be 1831 included in the check. If the UUID of the item to be checked is 1832 a temporarily generated one, this has to be C{False}. 1833 1834 """ 1835 if not item.uuid: 1836 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,)) 1837 if item.uuid in self._AllIDs(include_temporary=include_temporary): 1838 raise errors.ConfigurationError("Cannot add '%s': UUID %s already" 1839 " in use" % (item.name, item.uuid))
1840
1841 - def _SetInstanceStatus(self, inst_uuid, status, disks_active, 1842 admin_state_source):
1843 """Set the instance's status to a given value. 1844 1845 @rtype: L{objects.Instance} 1846 @return: the updated instance object 1847 1848 """ 1849 if inst_uuid not in self._ConfigData().instances: 1850 raise errors.ConfigurationError("Unknown instance '%s'" % 1851 inst_uuid) 1852 instance = self._ConfigData().instances[inst_uuid] 1853 1854 if status is None: 1855 status = instance.admin_state 1856 if disks_active is None: 1857 disks_active = instance.disks_active 1858 if admin_state_source is None: 1859 admin_state_source = instance.admin_state_source 1860 1861 assert status in constants.ADMINST_ALL, \ 1862 "Invalid status '%s' passed to SetInstanceStatus" % (status,) 1863 1864 if instance.admin_state != status or \ 1865 instance.disks_active != disks_active or \ 1866 instance.admin_state_source != admin_state_source: 1867 instance.admin_state = status 1868 instance.disks_active = disks_active 1869 instance.admin_state_source = admin_state_source 1870 instance.serial_no += 1 1871 instance.mtime = time.time() 1872 return instance
1873 1874 @_ConfigSync()
1875 - def MarkInstanceUp(self, inst_uuid):
1876 """Mark the instance status to up in the config. 1877 1878 This also sets the instance disks active flag. 1879 1880 @rtype: L{objects.Instance} 1881 @return: the updated instance object 1882 1883 """ 1884 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True, 1885 constants.ADMIN_SOURCE)
1886 1887 @_ConfigSync()
1888 - def MarkInstanceOffline(self, inst_uuid):
1889 """Mark the instance status to down in the config. 1890 1891 This also clears the instance disks active flag. 1892 1893 @rtype: L{objects.Instance} 1894 @return: the updated instance object 1895 1896 """ 1897 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False, 1898 constants.ADMIN_SOURCE)
1899 1900 @_ConfigSync()
1901 - def RemoveInstance(self, inst_uuid):
1902 """Remove the instance from the configuration. 1903 1904 """ 1905 if inst_uuid not in self._ConfigData().instances: 1906 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 1907 1908 # If a network port has been allocated to the instance, 1909 # return it to the pool of free ports. 1910 inst = self._ConfigData().instances[inst_uuid] 1911 network_port = getattr(inst, "network_port", None) 1912 if network_port is not None: 1913 self._ConfigData().cluster.tcpudp_port_pool.add(network_port) 1914 1915 instance = self._UnlockedGetInstanceInfo(inst_uuid) 1916 1917 # FIXME: After RemoveInstance is moved to WConfd, use its internal 1918 # function from TempRes module. 1919 for nic in instance.nics: 1920 if nic.network and nic.ip: 1921 # Return all IP addresses to the respective address pools 1922 self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip) 1923 1924 del self._ConfigData().instances[inst_uuid] 1925 self._ConfigData().cluster.serial_no += 1
1926 1927 @_ConfigSync()
1928 - def RenameInstance(self, inst_uuid, new_name):
1929 """Rename an instance. 1930 1931 This needs to be done in ConfigWriter and not by RemoveInstance 1932 combined with AddInstance as only we can guarantee an atomic 1933 rename. 1934 1935 """ 1936 if inst_uuid not in self._ConfigData().instances: 1937 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 1938 1939 inst = self._ConfigData().instances[inst_uuid] 1940 inst.name = new_name 1941 1942 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) 1943 for (_, disk) in enumerate(instance_disks): 1944 if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]: 1945 # rename the file paths in logical and physical id 1946 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1])) 1947 disk.logical_id = (disk.logical_id[0], 1948 utils.PathJoin(file_storage_dir, inst.name, 1949 os.path.basename(disk.logical_id[1]))) 1950 1951 # Force update of ssconf files 1952 self._ConfigData().cluster.serial_no += 1
1953 1954 @_ConfigSync()
1955 - def MarkInstanceDown(self, inst_uuid):
1956 """Mark the status of an instance to down in the configuration. 1957 1958 This does not touch the instance disks active flag, as shut down instances 1959 can still have active disks. 1960 1961 @rtype: L{objects.Instance} 1962 @return: the updated instance object 1963 1964 """ 1965 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None, 1966 constants.ADMIN_SOURCE)
1967 1968 @_ConfigSync()
1969 - def MarkInstanceUserDown(self, inst_uuid):
1970 """Mark the status of an instance to user down in the configuration. 1971 1972 This does not touch the instance disks active flag, as user shut 1973 down instances can still have active disks. 1974 1975 """ 1976 1977 self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None, 1978 constants.USER_SOURCE)
1979 1980 @_ConfigSync()
1981 - def MarkInstanceDisksActive(self, inst_uuid):
1982 """Mark the status of instance disks active. 1983 1984 @rtype: L{objects.Instance} 1985 @return: the updated instance object 1986 1987 """ 1988 return self._SetInstanceStatus(inst_uuid, None, True, None)
1989 1990 @_ConfigSync()
1991 - def MarkInstanceDisksInactive(self, inst_uuid):
1992 """Mark the status of instance disks inactive. 1993 1994 @rtype: L{objects.Instance} 1995 @return: the updated instance object 1996 1997 """ 1998 return self._SetInstanceStatus(inst_uuid, None, False, None)
1999
2000 - def _UnlockedGetInstanceList(self):
2001 """Get the list of instances. 2002 2003 This function is for internal use, when the config lock is already held. 2004 2005 """ 2006 return self._ConfigData().instances.keys()
2007 2008 @_ConfigSync(shared=1)
2009 - def GetInstanceList(self):
2010 """Get the list of instances. 2011 2012 @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid'] 2013 2014 """ 2015 return self._UnlockedGetInstanceList()
2016
2017 - def ExpandInstanceName(self, short_name):
2018 """Attempt to expand an incomplete instance name. 2019 2020 """ 2021 # Locking is done in L{ConfigWriter.GetAllInstancesInfo} 2022 all_insts = self.GetAllInstancesInfo().values() 2023 expanded_name = _MatchNameComponentIgnoreCase( 2024 short_name, [inst.name for inst in all_insts]) 2025 2026 if expanded_name is not None: 2027 # there has to be exactly one instance with that name 2028 inst = (filter(lambda n: n.name == expanded_name, all_insts)[0]) 2029 return (inst.uuid, inst.name) 2030 else: 2031 return (None, None)
2032
2033 - def _UnlockedGetInstanceInfo(self, inst_uuid):
2034 """Returns information about an instance. 2035 2036 This function is for internal use, when the config lock is already held. 2037 2038 """ 2039 if inst_uuid not in self._ConfigData().instances: 2040 return None 2041 2042 return self._ConfigData().instances[inst_uuid]
2043 2044 @_ConfigSync(shared=1)
2045 - def GetInstanceInfo(self, inst_uuid):
2046 """Returns information about an instance. 2047 2048 It takes the information from the configuration file. Other information of 2049 an instance are taken from the live systems. 2050 2051 @param inst_uuid: UUID of the instance 2052 2053 @rtype: L{objects.Instance} 2054 @return: the instance object 2055 2056 """ 2057 return self._UnlockedGetInstanceInfo(inst_uuid)
2058 2059 @_ConfigSync(shared=1)
2060 - def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
2061 """Returns set of node group UUIDs for instance's nodes. 2062 2063 @rtype: frozenset 2064 2065 """ 2066 instance = self._UnlockedGetInstanceInfo(inst_uuid) 2067 if not instance: 2068 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 2069 2070 if primary_only: 2071 nodes = [instance.primary_node] 2072 else: 2073 nodes = self._UnlockedGetInstanceNodes(instance.uuid) 2074 2075 return frozenset(self._UnlockedGetNodeInfo(node_uuid).group 2076 for node_uuid in nodes)
2077 2078 @_ConfigSync(shared=1)
2079 - def GetInstanceNetworks(self, inst_uuid):
2080 """Returns set of network UUIDs for instance's nics. 2081 2082 @rtype: frozenset 2083 2084 """ 2085 instance = self._UnlockedGetInstanceInfo(inst_uuid) 2086 if not instance: 2087 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid) 2088 2089 networks = set() 2090 for nic in instance.nics: 2091 if nic.network: 2092 networks.add(nic.network) 2093 2094 return frozenset(networks)
2095 2096 @_ConfigSync(shared=1)
2097 - def GetMultiInstanceInfo(self, inst_uuids):
2098 """Get the configuration of multiple instances. 2099 2100 @param inst_uuids: list of instance UUIDs 2101 @rtype: list 2102 @return: list of tuples (instance UUID, instance_info), where 2103 instance_info is what would GetInstanceInfo return for the 2104 node, while keeping the original order 2105 2106 """ 2107 return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
2108 2109 @_ConfigSync(shared=1)
2110 - def GetMultiInstanceInfoByName(self, inst_names):
2111 """Get the configuration of multiple instances. 2112 2113 @param inst_names: list of instance names 2114 @rtype: list 2115 @return: list of tuples (instance, instance_info), where 2116 instance_info is what would GetInstanceInfo return for the 2117 node, while keeping the original order 2118 2119 """ 2120 result = [] 2121 for name in inst_names: 2122 instance = self._UnlockedGetInstanceInfoByName(name) 2123 if instance: 2124 result.append((instance.uuid, instance)) 2125 else: 2126 raise errors.ConfigurationError("Instance data of instance '%s'" 2127 " not found." % name) 2128 return result
2129 2130 @_ConfigSync(shared=1)
2131 - def GetAllInstancesInfo(self):
2132 """Get the configuration of all instances. 2133 2134 @rtype: dict 2135 @return: dict of (instance, instance_info), where instance_info is what 2136 would GetInstanceInfo return for the node 2137 2138 """ 2139 return self._UnlockedGetAllInstancesInfo()
2140
2141 - def _UnlockedGetAllInstancesInfo(self):
2142 my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid)) 2143 for inst_uuid in self._UnlockedGetInstanceList()]) 2144 return my_dict
2145 2146 @_ConfigSync(shared=1)
2147 - def GetInstancesInfoByFilter(self, filter_fn):
2148 """Get instance configuration with a filter. 2149 2150 @type filter_fn: callable 2151 @param filter_fn: Filter function receiving instance object as parameter, 2152 returning boolean. Important: this function is called while the 2153 configuration locks is held. It must not do any complex work or call 2154 functions potentially leading to a deadlock. Ideally it doesn't call any 2155 other functions and just compares instance attributes. 2156 2157 """ 2158 return dict((uuid, inst) 2159 for (uuid, inst) in self._ConfigData().instances.items() 2160 if filter_fn(inst))
2161 2162 @_ConfigSync(shared=1)
2163 - def GetInstanceInfoByName(self, inst_name):
2164 """Get the L{objects.Instance} object for a named instance. 2165 2166 @param inst_name: name of the instance to get information for 2167 @type inst_name: string 2168 @return: the corresponding L{objects.Instance} instance or None if no 2169 information is available 2170 2171 """ 2172 return self._UnlockedGetInstanceInfoByName(inst_name)
2173
2174 - def _UnlockedGetInstanceInfoByName(self, inst_name):
2175 for inst in self._UnlockedGetAllInstancesInfo().values(): 2176 if inst.name == inst_name: 2177 return inst 2178 return None
2179
2180 - def _UnlockedGetInstanceName(self, inst_uuid):
2181 inst_info = self._UnlockedGetInstanceInfo(inst_uuid) 2182 if inst_info is None: 2183 raise errors.OpExecError("Unknown instance: %s" % inst_uuid) 2184 return inst_info.name
2185 2186 @_ConfigSync(shared=1)
2187 - def GetInstanceName(self, inst_uuid):
2188 """Gets the instance name for the passed instance. 2189 2190 @param inst_uuid: instance UUID to get name for 2191 @type inst_uuid: string 2192 @rtype: string 2193 @return: instance name 2194 2195 """ 2196 return self._UnlockedGetInstanceName(inst_uuid)
2197 2198 @_ConfigSync(shared=1)
2199 - def GetInstanceNames(self, inst_uuids):
2200 """Gets the instance names for the passed list of nodes. 2201 2202 @param inst_uuids: list of instance UUIDs to get names for 2203 @type inst_uuids: list of strings 2204 @rtype: list of strings 2205 @return: list of instance names 2206 2207 """ 2208 return self._UnlockedGetInstanceNames(inst_uuids)
2209 2210 @_ConfigSync()
2211 - def SetInstancePrimaryNode(self, inst_uuid, target_node_uuid):
2212 """Sets the primary node of an existing instance 2213 2214 @param inst_uuid: instance UUID 2215 @type inst_uuid: string 2216 @param target_node_uuid: the new primary node UUID 2217 @type target_node_uuid: string 2218 2219 """ 2220 self._UnlockedGetInstanceInfo(inst_uuid).primary_node = target_node_uuid
2221
2222 - def _UnlockedGetInstanceNames(self, inst_uuids):
2223 return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
2224
2225 - def _UnlockedAddNode(self, node, ec_id):
2226 """Add a node to the configuration. 2227 2228 @type node: L{objects.Node} 2229 @param node: a Node instance 2230 2231 """ 2232 logging.info("Adding node %s to configuration", node.name) 2233 2234 self._EnsureUUID(node, ec_id) 2235 2236 node.serial_no = 1 2237 node.ctime = node.mtime = time.time() 2238 self._UnlockedAddNodeToGroup(node.uuid, node.group) 2239 assert node.uuid in self._ConfigData().nodegroups[node.group].members 2240 self._ConfigData().nodes[node.uuid] = node 2241 self._ConfigData().cluster.serial_no += 1
2242 2243 @_ConfigSync()
2244 - def AddNode(self, node, ec_id):
2245 """Add a node to the configuration. 2246 2247 @type node: L{objects.Node} 2248 @param node: a Node instance 2249 2250 """ 2251 self._UnlockedAddNode(node, ec_id)
2252 2253 @_ConfigSync()
2254 - def RemoveNode(self, node_uuid):
2255 """Remove a node from the configuration. 2256 2257 """ 2258 logging.info("Removing node %s from configuration", node_uuid) 2259 2260 if node_uuid not in self._ConfigData().nodes: 2261 raise errors.ConfigurationError("Unknown node '%s'" % node_uuid) 2262 2263 self._UnlockedRemoveNodeFromGroup(self._ConfigData().nodes[node_uuid]) 2264 del self._ConfigData().nodes[node_uuid] 2265 self._ConfigData().cluster.serial_no += 1
2266
2267 - def ExpandNodeName(self, short_name):
2268 """Attempt to expand an incomplete node name into a node UUID. 2269 2270 """ 2271 # Locking is done in L{ConfigWriter.GetAllNodesInfo} 2272 all_nodes = self.GetAllNodesInfo().values() 2273 expanded_name = _MatchNameComponentIgnoreCase( 2274 short_name, [node.name for node in all_nodes]) 2275 2276 if expanded_name is not None: 2277 # there has to be exactly one node with that name 2278 node = (filter(lambda n: n.name == expanded_name, all_nodes)[0]) 2279 return (node.uuid, node.name) 2280 else: 2281 return (None, None)
2282
2283 - def _UnlockedGetNodeInfo(self, node_uuid):
2284 """Get the configuration of a node, as stored in the config. 2285 2286 This function is for internal use, when the config lock is already 2287 held. 2288 2289 @param node_uuid: the node UUID 2290 2291 @rtype: L{objects.Node} 2292 @return: the node object 2293 2294 """ 2295 if node_uuid not in self._ConfigData().nodes: 2296 return None 2297 2298 return self._ConfigData().nodes[node_uuid]
2299 2300 @_ConfigSync(shared=1)
2301 - def GetNodeInfo(self, node_uuid):
2302 """Get the configuration of a node, as stored in the config. 2303 2304 This is just a locked wrapper over L{_UnlockedGetNodeInfo}. 2305 2306 @param node_uuid: the node UUID 2307 2308 @rtype: L{objects.Node} 2309 @return: the node object 2310 2311 """ 2312 return self._UnlockedGetNodeInfo(node_uuid)
2313 2314 @_ConfigSync(shared=1)
2315 - def GetNodeInstances(self, node_uuid):
2316 """Get the instances of a node, as stored in the config. 2317 2318 @param node_uuid: the node UUID 2319 2320 @rtype: (list, list) 2321 @return: a tuple with two lists: the primary and the secondary instances 2322 2323 """ 2324 pri = [] 2325 sec = [] 2326 for inst in self._ConfigData().instances.values(): 2327 if inst.primary_node == node_uuid: 2328 pri.append(inst.uuid) 2329 if node_uuid in self._UnlockedGetInstanceSecondaryNodes(inst.uuid): 2330 sec.append(inst.uuid) 2331 return (pri, sec)
2332 2333 @_ConfigSync(shared=1)
2334 - def GetNodeGroupInstances(self, uuid, primary_only=False):
2335 """Get the instances of a node group. 2336 2337 @param uuid: Node group UUID 2338 @param primary_only: Whether to only consider primary nodes 2339 @rtype: frozenset 2340 @return: List of instance UUIDs in node group 2341 2342 """ 2343 if primary_only: 2344 nodes_fn = lambda inst: [inst.primary_node] 2345 else: 2346 nodes_fn = lambda inst: self._UnlockedGetInstanceNodes(inst.uuid) 2347 2348 return frozenset(inst.uuid 2349 for inst in self._ConfigData().instances.values() 2350 for node_uuid in nodes_fn(inst) 2351 if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
2352
2353 - def _UnlockedGetHvparamsString(self, hvname):
2354 """Return the string representation of the list of hyervisor parameters of 2355 the given hypervisor. 2356 2357 @see: C{GetHvparams} 2358 2359 """ 2360 result = "" 2361 hvparams = self._ConfigData().cluster.hvparams[hvname] 2362 for key in hvparams: 2363 result += "%s=%s\n" % (key, hvparams[key]) 2364 return result
2365 2366 @_ConfigSync(shared=1)
2367 - def GetHvparamsString(self, hvname):
2368 """Return the hypervisor parameters of the given hypervisor. 2369 2370 @type hvname: string 2371 @param hvname: name of a hypervisor 2372 @rtype: string 2373 @return: string containing key-value-pairs, one pair on each line; 2374 format: KEY=VALUE 2375 2376 """ 2377 return self._UnlockedGetHvparamsString(hvname)
2378
2379 - def _UnlockedGetNodeList(self):
2380 """Return the list of nodes which are in the configuration. 2381 2382 This function is for internal use, when the config lock is already 2383 held. 2384 2385 @rtype: list 2386 2387 """ 2388 return self._ConfigData().nodes.keys()
2389 2390 @_ConfigSync(shared=1)
2391 - def GetNodeList(self):
2392 """Return the list of nodes which are in the configuration. 2393 2394 """ 2395 return self._UnlockedGetNodeList()
2396
2397 - def _UnlockedGetOnlineNodeList(self):
2398 """Return the list of nodes which are online. 2399 2400 """ 2401 all_nodes = [self._UnlockedGetNodeInfo(node) 2402 for node in self._UnlockedGetNodeList()] 2403 return [node.uuid for node in all_nodes if not node.offline]
2404 2405 @_ConfigSync(shared=1)
2406 - def GetOnlineNodeList(self):
2407 """Return the list of nodes which are online. 2408 2409 """ 2410 return self._UnlockedGetOnlineNodeList()
2411 2412 @_ConfigSync(shared=1)
2413 - def GetVmCapableNodeList(self):
2414 """Return the list of nodes which are not vm capable. 2415 2416 """ 2417 all_nodes = [self._UnlockedGetNodeInfo(node) 2418 for node in self._UnlockedGetNodeList()] 2419 return [node.uuid for node in all_nodes if node.vm_capable]
2420 2421 @_ConfigSync(shared=1)
2422 - def GetNonVmCapableNodeList(self):
2423 """Return the list of nodes' uuids which are not vm capable. 2424 2425 """ 2426 all_nodes = [self._UnlockedGetNodeInfo(node) 2427 for node in self._UnlockedGetNodeList()] 2428 return [node.uuid for node in all_nodes if not node.vm_capable]
2429 2430 @_ConfigSync(shared=1)
2431 - def GetNonVmCapableNodeNameList(self):
2432 """Return the list of nodes' names which are not vm capable. 2433 2434 """ 2435 all_nodes = [self._UnlockedGetNodeInfo(node) 2436 for node in self._UnlockedGetNodeList()] 2437 return [node.name for node in all_nodes if not node.vm_capable]
2438 2439 @_ConfigSync(shared=1)
2440 - def GetMultiNodeInfo(self, node_uuids):
2441 """Get the configuration of multiple nodes. 2442 2443 @param node_uuids: list of node UUIDs 2444 @rtype: list 2445 @return: list of tuples of (node, node_info), where node_info is 2446 what would GetNodeInfo return for the node, in the original 2447 order 2448 2449 """ 2450 return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2451
2452 - def _UnlockedGetAllNodesInfo(self):
2453 """Gets configuration of all nodes. 2454 2455 @note: See L{GetAllNodesInfo} 2456 2457 """ 2458 return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid)) 2459 for node_uuid in self._UnlockedGetNodeList()])
2460 2461 @_ConfigSync(shared=1)
2462 - def GetAllNodesInfo(self):
2463 """Get the configuration of all nodes. 2464 2465 @rtype: dict 2466 @return: dict of (node, node_info), where node_info is what 2467 would GetNodeInfo return for the node 2468 2469 """ 2470 return self._UnlockedGetAllNodesInfo()
2471
2472 - def _UnlockedGetNodeInfoByName(self, node_name):
2473 for node in self._UnlockedGetAllNodesInfo().values(): 2474 if node.name == node_name: 2475 return node 2476 return None
2477 2478 @_ConfigSync(shared=1)
2479 - def GetNodeInfoByName(self, node_name):
2480 """Get the L{objects.Node} object for a named node. 2481 2482 @param node_name: name of the node to get information for 2483 @type node_name: string 2484 @return: the corresponding L{objects.Node} instance or None if no 2485 information is available 2486 2487 """ 2488 return self._UnlockedGetNodeInfoByName(node_name)
2489 2490 @_ConfigSync(shared=1)
2491 - def GetNodeGroupInfoByName(self, nodegroup_name):
2492 """Get the L{objects.NodeGroup} object for a named node group. 2493 2494 @param nodegroup_name: name of the node group to get information for 2495 @type nodegroup_name: string 2496 @return: the corresponding L{objects.NodeGroup} instance or None if no 2497 information is available 2498 2499 """ 2500 for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values(): 2501 if nodegroup.name == nodegroup_name: 2502 return nodegroup 2503 return None
2504
2505 - def _UnlockedGetNodeName(self, node_spec):
2506 if isinstance(node_spec, objects.Node): 2507 return node_spec.name 2508 elif isinstance(node_spec, basestring): 2509 node_info = self._UnlockedGetNodeInfo(node_spec) 2510 if node_info is None: 2511 raise errors.OpExecError("Unknown node: %s" % node_spec) 2512 return node_info.name 2513 else: 2514 raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2515 2516 @_ConfigSync(shared=1)
2517 - def GetNodeName(self, node_spec):
2518 """Gets the node name for the passed node. 2519 2520 @param node_spec: node to get names for 2521 @type node_spec: either node UUID or a L{objects.Node} object 2522 @rtype: string 2523 @return: node name 2524 2525 """ 2526 return self._UnlockedGetNodeName(node_spec)
2527
2528 - def _UnlockedGetNodeNames(self, node_specs):
2529 return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2530 2531 @_ConfigSync(shared=1)
2532 - def GetNodeNames(self, node_specs):
2533 """Gets the node names for the passed list of nodes. 2534 2535 @param node_specs: list of nodes to get names for 2536 @type node_specs: list of either node UUIDs or L{objects.Node} objects 2537 @rtype: list of strings 2538 @return: list of node names 2539 2540 """ 2541 return self._UnlockedGetNodeNames(node_specs)
2542 2543 @_ConfigSync(shared=1)
2544 - def GetNodeGroupsFromNodes(self, node_uuids):
2545 """Returns groups for a list of nodes. 2546 2547 @type node_uuids: list of string 2548 @param node_uuids: List of node UUIDs 2549 @rtype: frozenset 2550 2551 """ 2552 return frozenset(self._UnlockedGetNodeInfo(uuid).group 2553 for uuid in node_uuids)
2554
2555 - def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2556 """Get the number of current and maximum desired and possible candidates. 2557 2558 @type exceptions: list 2559 @param exceptions: if passed, list of nodes that should be ignored 2560 @rtype: tuple 2561 @return: tuple of (current, desired and possible, possible) 2562 2563 """ 2564 mc_now = mc_should = mc_max = 0 2565 for node in self._ConfigData().nodes.values(): 2566 if exceptions and node.uuid in exceptions: 2567 continue 2568 if not (node.offline or node.drained) and node.master_capable: 2569 mc_max += 1 2570 if node.master_candidate: 2571 mc_now += 1 2572 mc_should = min(mc_max, self._ConfigData().cluster.candidate_pool_size) 2573 return (mc_now, mc_should, mc_max)
2574 2575 @_ConfigSync(shared=1)
2576 - def GetMasterCandidateStats(self, exceptions=None):
2577 """Get the number of current and maximum possible candidates. 2578 2579 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}. 2580 2581 @type exceptions: list 2582 @param exceptions: if passed, list of nodes that should be ignored 2583 @rtype: tuple 2584 @return: tuple of (current, max) 2585 2586 """ 2587 return self._UnlockedGetMasterCandidateStats(exceptions)
2588 2589 @_ConfigSync()
2590 - def MaintainCandidatePool(self, exception_node_uuids):
2591 """Try to grow the candidate pool to the desired size. 2592 2593 @type exception_node_uuids: list 2594 @param exception_node_uuids: if passed, list of nodes that should be ignored 2595 @rtype: list 2596 @return: list with the adjusted nodes (L{objects.Node} instances) 2597 2598 """ 2599 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats( 2600 exception_node_uuids) 2601 mod_list = [] 2602 if mc_now < mc_max: 2603 node_list = self._ConfigData().nodes.keys() 2604 random.shuffle(node_list) 2605 for uuid in node_list: 2606 if mc_now >= mc_max: 2607 break 2608 node = self._ConfigData().nodes[uuid] 2609 if (node.master_candidate or node.offline or node.drained or 2610 node.uuid in exception_node_uuids or not node.master_capable): 2611 continue 2612 mod_list.append(node) 2613 node.master_candidate = True 2614 node.serial_no += 1 2615 mc_now += 1 2616 if mc_now != mc_max: 2617 # this should not happen 2618 logging.warning("Warning: MaintainCandidatePool didn't manage to" 2619 " fill the candidate pool (%d/%d)", mc_now, mc_max) 2620 if mod_list: 2621 self._ConfigData().cluster.serial_no += 1 2622 2623 return mod_list
2624
2625 - def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2626 """Add a given node to the specified group. 2627 2628 """ 2629 if nodegroup_uuid not in self._ConfigData().nodegroups: 2630 # This can happen if a node group gets deleted between its lookup and 2631 # when we're adding the first node to it, since we don't keep a lock in 2632 # the meantime. It's ok though, as we'll fail cleanly if the node group 2633 # is not found anymore. 2634 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid) 2635 if node_uuid not in self._ConfigData().nodegroups[nodegroup_uuid].members: 2636 self._ConfigData().nodegroups[nodegroup_uuid].members.append(node_uuid)
2637
2638 - def _UnlockedRemoveNodeFromGroup(self, node):
2639 """Remove a given node from its group. 2640 2641 """ 2642 nodegroup = node.group 2643 if nodegroup not in self._ConfigData().nodegroups: 2644 logging.warning("Warning: node '%s' has unknown node group '%s'" 2645 " (while being removed from it)", node.uuid, nodegroup) 2646 nodegroup_obj = self._ConfigData().nodegroups[nodegroup] 2647 if node.uuid not in nodegroup_obj.members: 2648 logging.warning("Warning: node '%s' not a member of its node group '%s'" 2649 " (while being removed from it)", node.uuid, nodegroup) 2650 else: 2651 nodegroup_obj.members.remove(node.uuid)
2652 2653 @_ConfigSync()
2654 - def AssignGroupNodes(self, mods):
2655 """Changes the group of a number of nodes. 2656 2657 @type mods: list of tuples; (node name, new group UUID) 2658 @param mods: Node membership modifications 2659 2660 """ 2661 groups = self._ConfigData().nodegroups 2662 nodes = self._ConfigData().nodes 2663 2664 resmod = [] 2665 2666 # Try to resolve UUIDs first 2667 for (node_uuid, new_group_uuid) in mods: 2668 try: 2669 node = nodes[node_uuid] 2670 except KeyError: 2671 raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid) 2672 2673 if node.group == new_group_uuid: 2674 # Node is being assigned to its current group 2675 logging.debug("Node '%s' was assigned to its current group (%s)", 2676 node_uuid, node.group) 2677 continue 2678 2679 # Try to find current group of node 2680 try: 2681 old_group = groups[node.group] 2682 except KeyError: 2683 raise errors.ConfigurationError("Unable to find old group '%s'" % 2684 node.group) 2685 2686 # Try to find new group for node 2687 try: 2688 new_group = groups[new_group_uuid] 2689 except KeyError: 2690 raise errors.ConfigurationError("Unable to find new group '%s'" % 2691 new_group_uuid) 2692 2693 assert node.uuid in old_group.members, \ 2694 ("Inconsistent configuration: node '%s' not listed in members for its" 2695 " old group '%s'" % (node.uuid, old_group.uuid)) 2696 assert node.uuid not in new_group.members, \ 2697 ("Inconsistent configuration: node '%s' already listed in members for" 2698 " its new group '%s'" % (node.uuid, new_group.uuid)) 2699 2700 resmod.append((node, old_group, new_group)) 2701 2702 # Apply changes 2703 for (node, old_group, new_group) in resmod: 2704 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \ 2705 "Assigning to current group is not possible" 2706 2707 node.group = new_group.uuid 2708 2709 # Update members of involved groups 2710 if node.uuid in old_group.members: 2711 old_group.members.remove(node.uuid) 2712 if node.uuid not in new_group.members: 2713 new_group.members.append(node.uuid) 2714 2715 # Update timestamps and serials (only once per node/group object) 2716 now = time.time() 2717 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142 2718 obj.serial_no += 1 2719 obj.mtime = now 2720 2721 # Force ssconf update 2722 self._ConfigData().cluster.serial_no += 1
2723
2724 - def _BumpSerialNo(self):
2725 """Bump up the serial number of the config. 2726 2727 """ 2728 self._ConfigData().serial_no += 1 2729 self._ConfigData().mtime = time.time()
2730
2731 - def _AllUUIDObjects(self):
2732 """Returns all objects with uuid attributes. 2733 2734 """ 2735 return (self._ConfigData().instances.values() + 2736 self._ConfigData().nodes.values() + 2737 self._ConfigData().nodegroups.values() + 2738 self._ConfigData().networks.values() + 2739 self._ConfigData().disks.values() + 2740 self._AllNICs() + 2741 [self._ConfigData().cluster])
2742
2743 - def GetConfigManager(self, shared=False):
2744 """Returns a ConfigManager, which is suitable to perform a synchronized 2745 block of configuration operations. 2746 2747 WARNING: This blocks all other configuration operations, so anything that 2748 runs inside the block should be very fast, preferably not using any IO. 2749 """ 2750 2751 return ConfigManager(self, shared)
2752
2753 - def _AddLockCount(self, count):
2754 self._lock_count += count 2755 return self._lock_count
2756
2757 - def _LockCount(self):
2758 return self._lock_count
2759
2760 - def _OpenConfig(self, shared):
2761 """Read the config data from WConfd or disk. 2762 2763 """ 2764 if self._AddLockCount(1) > 1: 2765 if self._lock_current_shared and not shared: 2766 self._AddLockCount(-1) 2767 raise errors.ConfigurationError("Can't request an exclusive" 2768 " configuration lock while holding" 2769 " shared") 2770 else: 2771 return # we already have the lock, do nothing 2772 else: 2773 self._lock_current_shared = shared 2774 # Read the configuration data. If offline, read the file directly. 2775 # If online, call WConfd. 2776 if self._offline: 2777 try: 2778 raw_data = utils.ReadFile(self._cfg_file) 2779 data_dict = serializer.Load(raw_data) 2780 # Make sure the configuration has the right version 2781 _ValidateConfig(data_dict) 2782 data = objects.ConfigData.FromDict(data_dict) 2783 except errors.ConfigVersionMismatch: 2784 raise 2785 except Exception, err: 2786 raise errors.ConfigurationError(err) 2787 2788 self._cfg_id = utils.GetFileID(path=self._cfg_file) 2789 2790 if (not hasattr(data, "cluster") or 2791 not hasattr(data.cluster, "rsahostkeypub")): 2792 raise errors.ConfigurationError("Incomplete configuration" 2793 " (missing cluster.rsahostkeypub)") 2794 2795 if not data.cluster.master_node in data.nodes: 2796 msg = ("The configuration denotes node %s as master, but does not" 2797 " contain information about this node" % 2798 data.cluster.master_node) 2799 raise errors.ConfigurationError(msg) 2800 2801 master_info = data.nodes[data.cluster.master_node] 2802 if master_info.name != self._my_hostname and not self._accept_foreign: 2803 msg = ("The configuration denotes node %s as master, while my" 2804 " hostname is %s; opening a foreign configuration is only" 2805 " possible in accept_foreign mode" % 2806 (master_info.name, self._my_hostname)) 2807 raise errors.ConfigurationError(msg) 2808 2809 self._SetConfigData(data) 2810 2811 # Upgrade configuration if needed 2812 self._UpgradeConfig(saveafter=True) 2813 else: 2814 if shared: 2815 if self._config_data is None: 2816 logging.debug("Requesting config, as I have no up-to-date copy") 2817 dict_data = self._wconfd.ReadConfig() 2818 else: 2819 logging.debug("My config copy is up to date.") 2820 dict_data = None 2821 else: 2822 # poll until we acquire the lock 2823 while True: 2824 dict_data = \ 2825 self._wconfd.LockConfig(self._GetWConfdContext(), bool(shared)) 2826 logging.debug("Received config from WConfd.LockConfig [shared=%s]", 2827 bool(shared)) 2828 if dict_data is not None: 2829 break 2830 time.sleep(random.random()) 2831 2832 try: 2833 if dict_data is not None: 2834 self._SetConfigData(objects.ConfigData.FromDict(dict_data)) 2835 self._UpgradeConfig() 2836 except Exception, err: 2837 raise errors.ConfigurationError(err)
2838
2839 - def _CloseConfig(self, save):
2840 """Release resources relating the config data. 2841 2842 """ 2843 if self._AddLockCount(-1) > 0: 2844 return # we still have the lock, do nothing 2845 try: 2846 if save: 2847 self._WriteConfig() 2848 except Exception, err: 2849 logging.critical("Can't write the configuration: %s", str(err)) 2850 raise 2851 finally: 2852 if not self._offline and not self._lock_current_shared: 2853 try: 2854 self._wconfd.UnlockConfig(self._GetWConfdContext()) 2855 except AttributeError: 2856 # If the configuration hasn't been initialized yet, just ignore it. 2857 pass 2858 logging.debug("Configuration in WConfd unlocked")
2859 2860 # TODO: To WConfd
2861 - def _UpgradeConfig(self, saveafter=False):
2862 """Run any upgrade steps. 2863 2864 This method performs both in-object upgrades and also update some data 2865 elements that need uniqueness across the whole configuration or interact 2866 with other objects. 2867 2868 @warning: if 'saveafter' is 'True', this function will call 2869 L{_WriteConfig()} so it needs to be called only from a 2870 "safe" place. 2871 2872 """ 2873 # Keep a copy of the persistent part of _config_data to check for changes 2874 # Serialization doesn't guarantee order in dictionaries 2875 oldconf = copy.deepcopy(self._ConfigData().ToDict()) 2876 2877 # In-object upgrades 2878 self._ConfigData().UpgradeConfig() 2879 2880 for item in self._AllUUIDObjects(): 2881 if item.uuid is None: 2882 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID) 2883 if not self._ConfigData().nodegroups: 2884 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME 2885 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name, 2886 members=[]) 2887 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True) 2888 for node in self._ConfigData().nodes.values(): 2889 if not node.group: 2890 node.group = self._UnlockedLookupNodeGroup(None) 2891 # This is technically *not* an upgrade, but needs to be done both when 2892 # nodegroups are being added, and upon normally loading the config, 2893 # because the members list of a node group is discarded upon 2894 # serializing/deserializing the object. 2895 self._UnlockedAddNodeToGroup(node.uuid, node.group) 2896 2897 modified = (oldconf != self._ConfigData().ToDict()) 2898 if modified and saveafter: 2899 self._WriteConfig() 2900 self._UnlockedDropECReservations(_UPGRADE_CONFIG_JID) 2901 else: 2902 if self._offline: 2903 self._UnlockedVerifyConfigAndLog()
2904
2905 - def _WriteConfig(self, destination=None):
2906 """Write the configuration data to persistent storage. 2907 2908 """ 2909 if destination is None: 2910 destination = self._cfg_file 2911 2912 # Save the configuration data. If offline, write the file directly. 2913 # If online, call WConfd. 2914 if self._offline: 2915 self._BumpSerialNo() 2916 txt = serializer.DumpJson( 2917 self._ConfigData().ToDict(_with_private=True), 2918 private_encoder=serializer.EncodeWithPrivateFields 2919 ) 2920 2921 getents = self._getents() 2922 try: 2923 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt, 2924 close=False, gid=getents.confd_gid, mode=0640) 2925 except errors.LockError: 2926 raise errors.ConfigurationError("The configuration file has been" 2927 " modified since the last write, cannot" 2928 " update") 2929 try: 2930 self._cfg_id = utils.GetFileID(fd=fd) 2931 finally: 2932 os.close(fd) 2933 else: 2934 try: 2935 self._wconfd.WriteConfig(self._GetWConfdContext(), 2936 self._ConfigData().ToDict()) 2937 except errors.LockError: 2938 raise errors.ConfigurationError("The configuration file has been" 2939 " modified since the last write, cannot" 2940 " update") 2941 2942 self.write_count += 1
2943
2944 - def _GetAllHvparamsStrings(self, hypervisors):
2945 """Get the hvparams of all given hypervisors from the config. 2946 2947 @type hypervisors: list of string 2948 @param hypervisors: list of hypervisor names 2949 @rtype: dict of strings 2950 @returns: dictionary mapping the hypervisor name to a string representation 2951 of the hypervisor's hvparams 2952 2953 """ 2954 hvparams = {} 2955 for hv in hypervisors: 2956 hvparams[hv] = self._UnlockedGetHvparamsString(hv) 2957 return hvparams
2958 2959 @staticmethod
2960 - def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2961 """Extends the ssconf_values dictionary by hvparams. 2962 2963 @type ssconf_values: dict of strings 2964 @param ssconf_values: dictionary mapping ssconf_keys to strings 2965 representing the content of ssconf files 2966 @type all_hvparams: dict of strings 2967 @param all_hvparams: dictionary mapping hypervisor names to a string 2968 representation of their hvparams 2969 @rtype: same as ssconf_values 2970 @returns: the ssconf_values dictionary extended by hvparams 2971 2972 """ 2973 for hv in all_hvparams: 2974 ssconf_key = constants.SS_HVPARAMS_PREF + hv 2975 ssconf_values[ssconf_key] = all_hvparams[hv] 2976 return ssconf_values
2977
2978 - def _UnlockedGetSshPortMap(self, node_infos):
2979 node_ports = dict([(node.name, 2980 self._UnlockedGetNdParams(node).get( 2981 constants.ND_SSH_PORT)) 2982 for node in node_infos]) 2983 return node_ports
2984
2985 - def _UnlockedGetSsconfValues(self):
2986 """Return the values needed by ssconf. 2987 2988 @rtype: dict 2989 @return: a dictionary with keys the ssconf names and values their 2990 associated value 2991 2992 """ 2993 fn = "\n".join 2994 instance_names = utils.NiceSort( 2995 [inst.name for inst in 2996 self._UnlockedGetAllInstancesInfo().values()]) 2997 node_infos = self._UnlockedGetAllNodesInfo().values() 2998 node_names = [node.name for node in node_infos] 2999 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip) 3000 for ninfo in node_infos] 3001 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip) 3002 for ninfo in node_infos] 3003 node_vm_capable = ["%s=%s" % (ninfo.name, str(ninfo.vm_capable)) 3004 for ninfo in node_infos] 3005 3006 instance_data = fn(instance_names) 3007 off_data = fn(node.name for node in node_infos if node.offline) 3008 on_data = fn(node.name for node in node_infos if not node.offline) 3009 mc_data = fn(node.name for node in node_infos if node.master_candidate) 3010 mc_ips_data = fn(node.primary_ip for node in node_infos 3011 if node.master_candidate) 3012 node_data = fn(node_names) 3013 node_pri_ips_data = fn(node_pri_ips) 3014 node_snd_ips_data = fn(node_snd_ips) 3015 node_vm_capable_data = fn(node_vm_capable) 3016 3017 cluster = self._ConfigData().cluster 3018 cluster_tags = fn(cluster.GetTags()) 3019 3020 master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert) 3021 for mc_uuid, mc_cert 3022 in cluster.candidate_certs.items()) 3023 3024 hypervisor_list = fn(cluster.enabled_hypervisors) 3025 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES) 3026 3027 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n") 3028 3029 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in 3030 self._ConfigData().nodegroups.values()] 3031 nodegroups_data = fn(utils.NiceSort(nodegroups)) 3032 networks = ["%s %s" % (net.uuid, net.name) for net in 3033 self._ConfigData().networks.values()] 3034 networks_data = fn(utils.NiceSort(networks)) 3035 3036 ssh_ports = fn("%s=%s" % (node_name, port) 3037 for node_name, port 3038 in self._UnlockedGetSshPortMap(node_infos).items()) 3039 3040 ssconf_values = { 3041 constants.SS_CLUSTER_NAME: cluster.cluster_name, 3042 constants.SS_CLUSTER_TAGS: cluster_tags, 3043 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir, 3044 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir, 3045 constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir, 3046 constants.SS_MASTER_CANDIDATES: mc_data, 3047 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data, 3048 constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs, 3049 constants.SS_MASTER_IP: cluster.master_ip, 3050 constants.SS_MASTER_NETDEV: cluster.master_netdev, 3051 constants.SS_MASTER_NETMASK: str(cluster.master_netmask), 3052 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node), 3053 constants.SS_NODE_LIST: node_data, 3054 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data, 3055 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data, 3056 constants.SS_NODE_VM_CAPABLE: node_vm_capable_data, 3057 constants.SS_OFFLINE_NODES: off_data, 3058 constants.SS_ONLINE_NODES: on_data, 3059 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family), 3060 constants.SS_INSTANCE_LIST: instance_data, 3061 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION, 3062 constants.SS_HYPERVISOR_LIST: hypervisor_list, 3063 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health), 3064 constants.SS_UID_POOL: uid_pool, 3065 constants.SS_NODEGROUPS: nodegroups_data, 3066 constants.SS_NETWORKS: networks_data, 3067 constants.SS_ENABLED_USER_SHUTDOWN: str(cluster.enabled_user_shutdown), 3068 constants.SS_SSH_PORTS: ssh_ports, 3069 } 3070 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values, 3071 all_hvparams) 3072 bad_values = [(k, v) for k, v in ssconf_values.items() 3073 if not isinstance(v, (str, basestring))] 3074 if bad_values: 3075 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values) 3076 raise errors.ConfigurationError("Some ssconf key(s) have non-string" 3077 " values: %s" % err) 3078 return ssconf_values
3079 3080 @_ConfigSync(shared=1)
3081 - def GetSsconfValues(self):
3082 """Wrapper using lock around _UnlockedGetSsconf(). 3083 3084 """ 3085 return self._UnlockedGetSsconfValues()
3086 3087 @_ConfigSync(shared=1)
3088 - def GetVGName(self):
3089 """Return the volume group name. 3090 3091 """ 3092 return self._ConfigData().cluster.volume_group_name
3093 3094 @_ConfigSync()
3095 - def SetVGName(self, vg_name):
3096 """Set the volume group name. 3097 3098 """ 3099 self._ConfigData().cluster.volume_group_name = vg_name 3100 self._ConfigData().cluster.serial_no += 1
3101 3102 @_ConfigSync(shared=1)
3103 - def GetDRBDHelper(self):
3104 """Return DRBD usermode helper. 3105 3106 """ 3107 return self._ConfigData().cluster.drbd_usermode_helper
3108 3109 @_ConfigSync()
3110 - def SetDRBDHelper(self, drbd_helper):
3111 """Set DRBD usermode helper. 3112 3113 """ 3114 self._ConfigData().cluster.drbd_usermode_helper = drbd_helper 3115 self._ConfigData().cluster.serial_no += 1
3116 3117 @_ConfigSync(shared=1)
3118 - def GetMACPrefix(self):
3119 """Return the mac prefix. 3120 3121 """ 3122 return self._ConfigData().cluster.mac_prefix
3123 3124 @_ConfigSync(shared=1)
3125 - def GetClusterInfo(self):
3126 """Returns information about the cluster 3127 3128 @rtype: L{objects.Cluster} 3129 @return: the cluster object 3130 3131 """ 3132 return self._ConfigData().cluster
3133 3134 @_ConfigSync(shared=1)
3135 - def HasAnyDiskOfType(self, dev_type):
3136 """Check if in there is at disk of the given type in the configuration. 3137 3138 """ 3139 return self._ConfigData().HasAnyDiskOfType(dev_type)
3140 3141 @_ConfigSync(shared=1)
3142 - def GetDetachedConfig(self):
3143 """Returns a detached version of a ConfigManager, which represents 3144 a read-only snapshot of the configuration at this particular time. 3145 3146 """ 3147 return DetachedConfig(self._ConfigData())
3148 3149 @_ConfigSync()
3150 - def Update(self, target, feedback_fn, ec_id=None):
3151 """Notify function to be called after updates. 3152 3153 This function must be called when an object (as returned by 3154 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the 3155 caller wants the modifications saved to the backing store. Note 3156 that all modified objects will be saved, but the target argument 3157 is the one the caller wants to ensure that it's saved. 3158 3159 @param target: an instance of either L{objects.Cluster}, 3160 L{objects.Node} or L{objects.Instance} which is existing in 3161 the cluster 3162 @param feedback_fn: Callable feedback function 3163 3164 """ 3165 if self._ConfigData() is None: 3166 raise errors.ProgrammerError("Configuration file not read," 3167 " cannot save.") 3168 3169 def check_serial(target, current): 3170 if current is None: 3171 raise errors.ConfigurationError("Configuration object unknown") 3172 elif current.serial_no != target.serial_no: 3173 raise errors.ConfigurationError("Configuration object updated since" 3174 " it has been read: %d != %d", 3175 current.serial_no, target.serial_no)
3176 3177 def replace_in(target, tdict): 3178 check_serial(target, tdict.get(target.uuid)) 3179 tdict[target.uuid] = target 3180 3181 update_serial = False 3182 if isinstance(target, objects.Cluster): 3183 check_serial(target, self._ConfigData().cluster) 3184 self._ConfigData().cluster = target 3185 elif isinstance(target, objects.Node): 3186 replace_in(target, self._ConfigData().nodes) 3187 update_serial = True 3188 elif isinstance(target, objects.Instance): 3189 replace_in(target, self._ConfigData().instances) 3190 elif isinstance(target, objects.NodeGroup): 3191 replace_in(target, self._ConfigData().nodegroups) 3192 elif isinstance(target, objects.Network): 3193 replace_in(target, self._ConfigData().networks) 3194 elif isinstance(target, objects.Disk): 3195 replace_in(target, self._ConfigData().disks) 3196 else: 3197 raise errors.ProgrammerError("Invalid object type (%s) passed to" 3198 " ConfigWriter.Update" % type(target)) 3199 target.serial_no += 1 3200 target.mtime = now = time.time() 3201 3202 if update_serial: 3203 # for node updates, we need to increase the cluster serial too 3204 self._ConfigData().cluster.serial_no += 1 3205 self._ConfigData().cluster.mtime = now 3206 3207 if isinstance(target, objects.Instance): 3208 self._UnlockedReleaseDRBDMinors(target.uuid) 3209 3210 if ec_id is not None: 3211 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams 3212 # FIXME: After RemoveInstance is moved to WConfd, use its internal 3213 # functions from TempRes module. 3214 self._UnlockedCommitTemporaryIps(ec_id) 3215 3216 # Just verify the configuration with our feedback function. 3217 # It will get written automatically by the decorator. 3218 self._UnlockedVerifyConfigAndLog(feedback_fn=feedback_fn) 3219
3220 - def _UnlockedDropECReservations(self, _ec_id):
3221 """Drop per-execution-context reservations 3222 3223 """ 3224 # FIXME: Remove the following two lines after all reservations are moved to 3225 # wconfd. 3226 for rm in self._all_rms: 3227 rm.DropECReservations(_ec_id) 3228 if not self._offline: 3229 self._wconfd.DropAllReservations(self._GetWConfdContext())
3230
3231 - def DropECReservations(self, ec_id):
3232 self._UnlockedDropECReservations(ec_id)
3233 3234 @_ConfigSync(shared=1)
3235 - def GetAllNetworksInfo(self):
3236 """Get configuration info of all the networks. 3237 3238 """ 3239 return dict(self._ConfigData().networks)
3240
3241 - def _UnlockedGetNetworkList(self):
3242 """Get the list of networks. 3243 3244 This function is for internal use, when the config lock is already held. 3245 3246 """ 3247 return self._ConfigData().networks.keys()
3248 3249 @_ConfigSync(shared=1)
3250 - def GetNetworkList(self):
3251 """Get the list of networks. 3252 3253 @return: array of networks, ex. ["main", "vlan100", "200] 3254 3255 """ 3256 return self._UnlockedGetNetworkList()
3257 3258 @_ConfigSync(shared=1)
3259 - def GetNetworkNames(self):
3260 """Get a list of network names 3261 3262 """ 3263 names = [net.name 3264 for net in self._ConfigData().networks.values()] 3265 return names
3266
3267 - def _UnlockedGetNetwork(self, uuid):
3268 """Returns information about a network. 3269 3270 This function is for internal use, when the config lock is already held. 3271 3272 """ 3273 if uuid not in self._ConfigData().networks: 3274 return None 3275 3276 return self._ConfigData().networks[uuid]
3277 3278 @_ConfigSync(shared=1)
3279 - def GetNetwork(self, uuid):
3280 """Returns information about a network. 3281 3282 It takes the information from the configuration file. 3283 3284 @param uuid: UUID of the network 3285 3286 @rtype: L{objects.Network} 3287 @return: the network object 3288 3289 """ 3290 return self._UnlockedGetNetwork(uuid)
3291 3292 @_ConfigSync()
3293 - def AddNetwork(self, net, ec_id, check_uuid=True):
3294 """Add a network to the configuration. 3295 3296 @type net: L{objects.Network} 3297 @param net: the Network object to add 3298 @type ec_id: string 3299 @param ec_id: unique id for the job to use when creating a missing UUID 3300 3301 """ 3302 self._UnlockedAddNetwork(net, ec_id