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