Package ganeti :: Package cmdlib :: Package cluster
[hide private]
[frames] | no frames]

Source Code for Package ganeti.cmdlib.cluster

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  29   
  30   
  31  """Logical units dealing with the cluster.""" 
  32   
  33  import copy 
  34  import itertools 
  35  import logging 
  36  import operator 
  37  import os 
  38  import re 
  39  import time 
  40   
  41  from ganeti import compat 
  42  from ganeti import constants 
  43  from ganeti import errors 
  44  from ganeti import hypervisor 
  45  from ganeti import locking 
  46  from ganeti import masterd 
  47  from ganeti import netutils 
  48  from ganeti import objects 
  49  from ganeti import opcodes 
  50  from ganeti import pathutils 
  51  from ganeti import query 
  52  import ganeti.rpc.node as rpc 
  53  from ganeti import runtime 
  54  from ganeti import ssh 
  55  from ganeti import uidpool 
  56  from ganeti import utils 
  57  from ganeti import vcluster 
  58   
  59  from ganeti.cmdlib.base import NoHooksLU, QueryBase, LogicalUnit, \ 
  60    ResultWithJobs 
  61  from ganeti.cmdlib.common import ShareAll, RunPostHook, \ 
  62    ComputeAncillaryFiles, RedistributeAncillaryFiles, UploadHelper, \ 
  63    GetWantedInstances, MergeAndVerifyHvState, MergeAndVerifyDiskState, \ 
  64    GetUpdatedIPolicy, ComputeNewInstanceViolations, GetUpdatedParams, \ 
  65    CheckOSParams, CheckHVParams, AdjustCandidatePool, CheckNodePVs, \ 
  66    ComputeIPolicyInstanceViolation, AnnotateDiskParams, SupportsOob, \ 
  67    CheckIpolicyVsDiskTemplates, CheckDiskAccessModeValidity, \ 
  68    CheckDiskAccessModeConsistency, GetClientCertDigest, \ 
  69    AddInstanceCommunicationNetworkOp, ConnectInstanceCommunicationNetworkOp, \ 
  70    CheckImageValidity, CheckDiskAccessModeConsistency, EnsureKvmdOnNodes 
  71   
  72  import ganeti.masterd.instance 
73 74 75 -class LUClusterRenewCrypto(NoHooksLU):
76 """Renew the cluster's crypto tokens. 77 78 """ 79 80 _MAX_NUM_RETRIES = 3 81 REQ_BGL = False 82
83 - def ExpandNames(self):
84 self.needed_locks = { 85 locking.LEVEL_NODE: locking.ALL_SET, 86 } 87 self.share_locks = ShareAll() 88 self.share_locks[locking.LEVEL_NODE] = 0
89
90 - def CheckPrereq(self):
91 """Check prerequisites. 92 93 Notably the compatibility of specified key bits and key type. 94 95 """ 96 cluster_info = self.cfg.GetClusterInfo() 97 98 self.ssh_key_type = self.op.ssh_key_type 99 if self.ssh_key_type is None: 100 self.ssh_key_type = cluster_info.ssh_key_type 101 102 self.ssh_key_bits = ssh.DetermineKeyBits(self.ssh_key_type, 103 self.op.ssh_key_bits, 104 cluster_info.ssh_key_type, 105 cluster_info.ssh_key_bits)
106
107 - def _RenewNodeSslCertificates(self, feedback_fn):
108 """Renews the nodes' SSL certificates. 109 110 Note that most of this operation is done in gnt_cluster.py, this LU only 111 takes care of the renewal of the client SSL certificates. 112 113 """ 114 master_uuid = self.cfg.GetMasterNode() 115 cluster = self.cfg.GetClusterInfo() 116 117 logging.debug("Renewing the master's SSL node certificate." 118 " Master's UUID: %s.", master_uuid) 119 120 # mapping node UUIDs to client certificate digests 121 digest_map = {} 122 master_digest = utils.GetCertificateDigest( 123 cert_filename=pathutils.NODED_CLIENT_CERT_FILE) 124 digest_map[master_uuid] = master_digest 125 logging.debug("Adding the master's SSL node certificate digest to the" 126 " configuration. Master's UUID: %s, Digest: %s", 127 master_uuid, master_digest) 128 129 node_errors = {} 130 nodes = self.cfg.GetAllNodesInfo() 131 logging.debug("Renewing non-master nodes' node certificates.") 132 for (node_uuid, node_info) in nodes.items(): 133 if node_info.offline: 134 logging.info("* Skipping offline node %s", node_info.name) 135 continue 136 if node_uuid != master_uuid: 137 logging.debug("Adding certificate digest of node '%s'.", node_uuid) 138 last_exception = None 139 for i in range(self._MAX_NUM_RETRIES): 140 try: 141 if node_info.master_candidate: 142 node_digest = GetClientCertDigest(self, node_uuid) 143 digest_map[node_uuid] = node_digest 144 logging.debug("Added the node's certificate to candidate" 145 " certificate list. Current list: %s.", 146 str(cluster.candidate_certs)) 147 break 148 except errors.OpExecError as e: 149 last_exception = e 150 logging.error("Could not fetch a non-master node's SSL node" 151 " certificate at attempt no. %s. The node's UUID" 152 " is %s, and the error was: %s.", 153 str(i), node_uuid, e) 154 else: 155 if last_exception: 156 node_errors[node_uuid] = last_exception 157 158 if node_errors: 159 msg = ("Some nodes' SSL client certificates could not be fetched." 160 " Please make sure those nodes are reachable and rerun" 161 " the operation. The affected nodes and their errors are:\n") 162 for uuid, e in node_errors.items(): 163 msg += "Node %s: %s\n" % (uuid, e) 164 feedback_fn(msg) 165 166 self.cfg.SetCandidateCerts(digest_map)
167
168 - def _RenewSshKeys(self, feedback_fn):
169 """Renew all nodes' SSH keys. 170 171 @type feedback_fn: function 172 @param feedback_fn: logging function, see L{ganeti.cmdlist.base.LogicalUnit} 173 174 """ 175 master_uuid = self.cfg.GetMasterNode() 176 177 nodes = self.cfg.GetAllNodesInfo() 178 nodes_uuid_names = [(node_uuid, node_info.name) for (node_uuid, node_info) 179 in nodes.items() if not node_info.offline] 180 node_names = [name for (_, name) in nodes_uuid_names] 181 node_uuids = [uuid for (uuid, _) in nodes_uuid_names] 182 potential_master_candidates = self.cfg.GetPotentialMasterCandidates() 183 master_candidate_uuids = self.cfg.GetMasterCandidateUuids() 184 185 cluster_info = self.cfg.GetClusterInfo() 186 187 result = self.rpc.call_node_ssh_keys_renew( 188 [master_uuid], 189 node_uuids, node_names, 190 master_candidate_uuids, 191 potential_master_candidates, 192 cluster_info.ssh_key_type, # Old key type 193 self.ssh_key_type, # New key type 194 self.ssh_key_bits) # New key bits 195 result[master_uuid].Raise("Could not renew the SSH keys of all nodes") 196 197 # After the keys have been successfully swapped, time to commit the change 198 # in key type 199 cluster_info.ssh_key_type = self.ssh_key_type 200 cluster_info.ssh_key_bits = self.ssh_key_bits 201 self.cfg.Update(cluster_info, feedback_fn)
202
203 - def Exec(self, feedback_fn):
204 if self.op.node_certificates: 205 feedback_fn("Renewing Node SSL certificates") 206 self._RenewNodeSslCertificates(feedback_fn) 207 208 if self.op.renew_ssh_keys: 209 if self.cfg.GetClusterInfo().modify_ssh_setup: 210 feedback_fn("Renewing SSH keys") 211 self._RenewSshKeys(feedback_fn) 212 else: 213 feedback_fn("Cannot renew SSH keys if the cluster is configured to not" 214 " modify the SSH setup.")
215
216 217 -class LUClusterActivateMasterIp(NoHooksLU):
218 """Activate the master IP on the master node. 219 220 """
221 - def Exec(self, feedback_fn):
222 """Activate the master IP. 223 224 """ 225 master_params = self.cfg.GetMasterNetworkParameters() 226 ems = self.cfg.GetUseExternalMipScript() 227 result = self.rpc.call_node_activate_master_ip(master_params.uuid, 228 master_params, ems) 229 result.Raise("Could not activate the master IP")
230
231 232 -class LUClusterDeactivateMasterIp(NoHooksLU):
233 """Deactivate the master IP on the master node. 234 235 """
236 - def Exec(self, feedback_fn):
237 """Deactivate the master IP. 238 239 """ 240 master_params = self.cfg.GetMasterNetworkParameters() 241 ems = self.cfg.GetUseExternalMipScript() 242 result = self.rpc.call_node_deactivate_master_ip(master_params.uuid, 243 master_params, ems) 244 result.Raise("Could not deactivate the master IP")
245
246 247 -class LUClusterConfigQuery(NoHooksLU):
248 """Return configuration values. 249 250 """ 251 REQ_BGL = False 252
253 - def CheckArguments(self):
254 self.cq = ClusterQuery(None, self.op.output_fields, False)
255
256 - def ExpandNames(self):
257 self.cq.ExpandNames(self)
258
259 - def DeclareLocks(self, level):
260 self.cq.DeclareLocks(self, level)
261
262 - def Exec(self, feedback_fn):
263 result = self.cq.OldStyleQuery(self) 264 265 assert len(result) == 1 266 267 return result[0]
268
269 270 -class LUClusterDestroy(LogicalUnit):
271 """Logical unit for destroying the cluster. 272 273 """ 274 HPATH = "cluster-destroy" 275 HTYPE = constants.HTYPE_CLUSTER 276 277 # Read by the job queue to detect when the cluster is gone and job files will 278 # never be available. 279 # FIXME: This variable should be removed together with the Python job queue. 280 clusterHasBeenDestroyed = False 281
282 - def BuildHooksEnv(self):
283 """Build hooks env. 284 285 """ 286 return { 287 "OP_TARGET": self.cfg.GetClusterName(), 288 }
289
290 - def BuildHooksNodes(self):
291 """Build hooks nodes. 292 293 """ 294 return ([], [])
295
296 - def CheckPrereq(self):
297 """Check prerequisites. 298 299 This checks whether the cluster is empty. 300 301 Any errors are signaled by raising errors.OpPrereqError. 302 303 """ 304 master = self.cfg.GetMasterNode() 305 306 nodelist = self.cfg.GetNodeList() 307 if len(nodelist) != 1 or nodelist[0] != master: 308 raise errors.OpPrereqError("There are still %d node(s) in" 309 " this cluster." % (len(nodelist) - 1), 310 errors.ECODE_INVAL) 311 instancelist = self.cfg.GetInstanceList() 312 if instancelist: 313 raise errors.OpPrereqError("There are still %d instance(s) in" 314 " this cluster." % len(instancelist), 315 errors.ECODE_INVAL)
316
317 - def Exec(self, feedback_fn):
318 """Destroys the cluster. 319 320 """ 321 master_params = self.cfg.GetMasterNetworkParameters() 322 323 # Run post hooks on master node before it's removed 324 RunPostHook(self, self.cfg.GetNodeName(master_params.uuid)) 325 326 ems = self.cfg.GetUseExternalMipScript() 327 result = self.rpc.call_node_deactivate_master_ip(master_params.uuid, 328 master_params, ems) 329 result.Warn("Error disabling the master IP address", self.LogWarning) 330 331 self.wconfd.Client().PrepareClusterDestruction(self.wconfdcontext) 332 333 # signal to the job queue that the cluster is gone 334 LUClusterDestroy.clusterHasBeenDestroyed = True 335 336 return master_params.uuid
337
338 339 -class LUClusterPostInit(LogicalUnit):
340 """Logical unit for running hooks after cluster initialization. 341 342 """ 343 HPATH = "cluster-init" 344 HTYPE = constants.HTYPE_CLUSTER 345
346 - def CheckArguments(self):
347 self.master_uuid = self.cfg.GetMasterNode() 348 self.master_ndparams = self.cfg.GetNdParams(self.cfg.GetMasterNodeInfo()) 349 350 # TODO: When Issue 584 is solved, and None is properly parsed when used 351 # as a default value, ndparams.get(.., None) can be changed to 352 # ndparams[..] to access the values directly 353 354 # OpenvSwitch: Warn user if link is missing 355 if (self.master_ndparams[constants.ND_OVS] and not 356 self.master_ndparams.get(constants.ND_OVS_LINK, None)): 357 self.LogInfo("No physical interface for OpenvSwitch was given." 358 " OpenvSwitch will not have an outside connection. This" 359 " might not be what you want.")
360
361 - def BuildHooksEnv(self):
362 """Build hooks env. 363 364 """ 365 return { 366 "OP_TARGET": self.cfg.GetClusterName(), 367 }
368
369 - def BuildHooksNodes(self):
370 """Build hooks nodes. 371 372 """ 373 return ([], [self.cfg.GetMasterNode()])
374
375 - def Exec(self, feedback_fn):
376 """Create and configure Open vSwitch 377 378 """ 379 if self.master_ndparams[constants.ND_OVS]: 380 result = self.rpc.call_node_configure_ovs( 381 self.master_uuid, 382 self.master_ndparams[constants.ND_OVS_NAME], 383 self.master_ndparams.get(constants.ND_OVS_LINK, None)) 384 result.Raise("Could not successully configure Open vSwitch") 385 386 return True
387
388 389 -class ClusterQuery(QueryBase):
390 FIELDS = query.CLUSTER_FIELDS 391 392 #: Do not sort (there is only one item) 393 SORT_FIELD = None 394
395 - def ExpandNames(self, lu):
396 lu.needed_locks = {} 397 398 # The following variables interact with _QueryBase._GetNames 399 self.wanted = locking.ALL_SET 400 self.do_locking = self.use_locking 401 402 if self.do_locking: 403 raise errors.OpPrereqError("Can not use locking for cluster queries", 404 errors.ECODE_INVAL)
405
406 - def DeclareLocks(self, lu, level):
407 pass
408
409 - def _GetQueryData(self, lu):
410 """Computes the list of nodes and their attributes. 411 412 """ 413 if query.CQ_CONFIG in self.requested_data: 414 cluster = lu.cfg.GetClusterInfo() 415 nodes = lu.cfg.GetAllNodesInfo() 416 else: 417 cluster = NotImplemented 418 nodes = NotImplemented 419 420 if query.CQ_QUEUE_DRAINED in self.requested_data: 421 drain_flag = os.path.exists(pathutils.JOB_QUEUE_DRAIN_FILE) 422 else: 423 drain_flag = NotImplemented 424 425 if query.CQ_WATCHER_PAUSE in self.requested_data: 426 master_node_uuid = lu.cfg.GetMasterNode() 427 428 result = lu.rpc.call_get_watcher_pause(master_node_uuid) 429 result.Raise("Can't retrieve watcher pause from master node '%s'" % 430 lu.cfg.GetMasterNodeName()) 431 432 watcher_pause = result.payload 433 else: 434 watcher_pause = NotImplemented 435 436 return query.ClusterQueryData(cluster, nodes, drain_flag, watcher_pause)
437
438 439 -class LUClusterQuery(NoHooksLU):
440 """Query cluster configuration. 441 442 """ 443 REQ_BGL = False 444
445 - def ExpandNames(self):
446 self.needed_locks = {}
447
448 - def Exec(self, feedback_fn):
449 """Return cluster config. 450 451 """ 452 cluster = self.cfg.GetClusterInfo() 453 os_hvp = {} 454 455 # Filter just for enabled hypervisors 456 for os_name, hv_dict in cluster.os_hvp.items(): 457 os_hvp[os_name] = {} 458 for hv_name, hv_params in hv_dict.items(): 459 if hv_name in cluster.enabled_hypervisors: 460 os_hvp[os_name][hv_name] = hv_params 461 462 # Convert ip_family to ip_version 463 primary_ip_version = constants.IP4_VERSION 464 if cluster.primary_ip_family == netutils.IP6Address.family: 465 primary_ip_version = constants.IP6_VERSION 466 467 result = { 468 "software_version": constants.RELEASE_VERSION, 469 "protocol_version": constants.PROTOCOL_VERSION, 470 "config_version": constants.CONFIG_VERSION, 471 "os_api_version": max(constants.OS_API_VERSIONS), 472 "export_version": constants.EXPORT_VERSION, 473 "vcs_version": constants.VCS_VERSION, 474 "architecture": runtime.GetArchInfo(), 475 "name": cluster.cluster_name, 476 "master": self.cfg.GetMasterNodeName(), 477 "default_hypervisor": cluster.primary_hypervisor, 478 "enabled_hypervisors": cluster.enabled_hypervisors, 479 "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name]) 480 for hypervisor_name in cluster.enabled_hypervisors]), 481 "os_hvp": os_hvp, 482 "beparams": cluster.beparams, 483 "osparams": cluster.osparams, 484 "ipolicy": cluster.ipolicy, 485 "nicparams": cluster.nicparams, 486 "ndparams": cluster.ndparams, 487 "diskparams": cluster.diskparams, 488 "candidate_pool_size": cluster.candidate_pool_size, 489 "max_running_jobs": cluster.max_running_jobs, 490 "max_tracked_jobs": cluster.max_tracked_jobs, 491 "mac_prefix": cluster.mac_prefix, 492 "master_netdev": cluster.master_netdev, 493 "master_netmask": cluster.master_netmask, 494 "use_external_mip_script": cluster.use_external_mip_script, 495 "volume_group_name": cluster.volume_group_name, 496 "drbd_usermode_helper": cluster.drbd_usermode_helper, 497 "file_storage_dir": cluster.file_storage_dir, 498 "shared_file_storage_dir": cluster.shared_file_storage_dir, 499 "maintain_node_health": cluster.maintain_node_health, 500 "ctime": cluster.ctime, 501 "mtime": cluster.mtime, 502 "uuid": cluster.uuid, 503 "tags": list(cluster.GetTags()), 504 "uid_pool": cluster.uid_pool, 505 "default_iallocator": cluster.default_iallocator, 506 "default_iallocator_params": cluster.default_iallocator_params, 507 "reserved_lvs": cluster.reserved_lvs, 508 "primary_ip_version": primary_ip_version, 509 "prealloc_wipe_disks": cluster.prealloc_wipe_disks, 510 "hidden_os": cluster.hidden_os, 511 "blacklisted_os": cluster.blacklisted_os, 512 "enabled_disk_templates": cluster.enabled_disk_templates, 513 "install_image": cluster.install_image, 514 "instance_communication_network": cluster.instance_communication_network, 515 "compression_tools": cluster.compression_tools, 516 "enabled_user_shutdown": cluster.enabled_user_shutdown, 517 } 518 519 return result
520
521 522 -class LUClusterRedistConf(NoHooksLU):
523 """Force the redistribution of cluster configuration. 524 525 This is a very simple LU. 526 527 """ 528 REQ_BGL = False 529
530 - def ExpandNames(self):
531 self.needed_locks = { 532 locking.LEVEL_NODE: locking.ALL_SET, 533 } 534 self.share_locks = ShareAll()
535
536 - def Exec(self, feedback_fn):
537 """Redistribute the configuration. 538 539 """ 540 self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn) 541 RedistributeAncillaryFiles(self)
542
543 544 -class LUClusterRename(LogicalUnit):
545 """Rename the cluster. 546 547 """ 548 HPATH = "cluster-rename" 549 HTYPE = constants.HTYPE_CLUSTER 550
551 - def BuildHooksEnv(self):
552 """Build hooks env. 553 554 """ 555 return { 556 "OP_TARGET": self.cfg.GetClusterName(), 557 "NEW_NAME": self.op.name, 558 }
559
560 - def BuildHooksNodes(self):
561 """Build hooks nodes. 562 563 """ 564 return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
565
566 - def CheckPrereq(self):
567 """Verify that the passed name is a valid one. 568 569 """ 570 hostname = netutils.GetHostname(name=self.op.name, 571 family=self.cfg.GetPrimaryIPFamily()) 572 573 new_name = hostname.name 574 self.ip = new_ip = hostname.ip 575 old_name = self.cfg.GetClusterName() 576 old_ip = self.cfg.GetMasterIP() 577 if new_name == old_name and new_ip == old_ip: 578 raise errors.OpPrereqError("Neither the name nor the IP address of the" 579 " cluster has changed", 580 errors.ECODE_INVAL) 581 if new_ip != old_ip: 582 if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT): 583 raise errors.OpPrereqError("The given cluster IP address (%s) is" 584 " reachable on the network" % 585 new_ip, errors.ECODE_NOTUNIQUE) 586 587 self.op.name = new_name
588
589 - def Exec(self, feedback_fn):
590 """Rename the cluster. 591 592 """ 593 clustername = self.op.name 594 new_ip = self.ip 595 596 # shutdown the master IP 597 master_params = self.cfg.GetMasterNetworkParameters() 598 ems = self.cfg.GetUseExternalMipScript() 599 result = self.rpc.call_node_deactivate_master_ip(master_params.uuid, 600 master_params, ems) 601 result.Raise("Could not disable the master role") 602 603 try: 604 cluster = self.cfg.GetClusterInfo() 605 cluster.cluster_name = clustername 606 cluster.master_ip = new_ip 607 self.cfg.Update(cluster, feedback_fn) 608 609 # update the known hosts file 610 ssh.WriteKnownHostsFile(self.cfg, pathutils.SSH_KNOWN_HOSTS_FILE) 611 node_list = self.cfg.GetOnlineNodeList() 612 try: 613 node_list.remove(master_params.uuid) 614 except ValueError: 615 pass 616 UploadHelper(self, node_list, pathutils.SSH_KNOWN_HOSTS_FILE) 617 finally: 618 master_params.ip = new_ip 619 result = self.rpc.call_node_activate_master_ip(master_params.uuid, 620 master_params, ems) 621 result.Warn("Could not re-enable the master role on the master," 622 " please restart manually", self.LogWarning) 623 624 return clustername
625
626 627 -class LUClusterRepairDiskSizes(NoHooksLU):
628 """Verifies the cluster disks sizes. 629 630 """ 631 REQ_BGL = False 632
633 - def ExpandNames(self):
634 if self.op.instances: 635 (_, self.wanted_names) = GetWantedInstances(self, self.op.instances) 636 # Not getting the node allocation lock as only a specific set of 637 # instances (and their nodes) is going to be acquired 638 self.needed_locks = { 639 locking.LEVEL_NODE_RES: [], 640 locking.LEVEL_INSTANCE: self.wanted_names, 641 } 642 self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE 643 else: 644 self.wanted_names = None 645 self.needed_locks = { 646 locking.LEVEL_NODE_RES: locking.ALL_SET, 647 locking.LEVEL_INSTANCE: locking.ALL_SET, 648 } 649 650 self.share_locks = { 651 locking.LEVEL_NODE_RES: 1, 652 locking.LEVEL_INSTANCE: 0, 653 }
654
655 - def DeclareLocks(self, level):
656 if level == locking.LEVEL_NODE_RES and self.wanted_names is not None: 657 self._LockInstancesNodes(primary_only=True, level=level)
658
659 - def CheckPrereq(self):
660 """Check prerequisites. 661 662 This only checks the optional instance list against the existing names. 663 664 """ 665 if self.wanted_names is None: 666 self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE) 667 668 self.wanted_instances = \ 669 map(compat.snd, self.cfg.GetMultiInstanceInfoByName(self.wanted_names))
670
671 - def _EnsureChildSizes(self, disk):
672 """Ensure children of the disk have the needed disk size. 673 674 This is valid mainly for DRBD8 and fixes an issue where the 675 children have smaller disk size. 676 677 @param disk: an L{ganeti.objects.Disk} object 678 679 """ 680 if disk.dev_type == constants.DT_DRBD8: 681 assert disk.children, "Empty children for DRBD8?" 682 fchild = disk.children[0] 683 mismatch = fchild.size < disk.size 684 if mismatch: 685 self.LogInfo("Child disk has size %d, parent %d, fixing", 686 fchild.size, disk.size) 687 fchild.size = disk.size 688 689 # and we recurse on this child only, not on the metadev 690 return self._EnsureChildSizes(fchild) or mismatch 691 else: 692 return False
693
694 - def Exec(self, feedback_fn):
695 """Verify the size of cluster disks. 696 697 """ 698 # TODO: check child disks too 699 # TODO: check differences in size between primary/secondary nodes 700 per_node_disks = {} 701 for instance in self.wanted_instances: 702 pnode = instance.primary_node 703 if pnode not in per_node_disks: 704 per_node_disks[pnode] = [] 705 for idx, disk in enumerate(self.cfg.GetInstanceDisks(instance.uuid)): 706 per_node_disks[pnode].append((instance, idx, disk)) 707 708 assert not (frozenset(per_node_disks.keys()) - 709 frozenset(self.owned_locks(locking.LEVEL_NODE_RES))), \ 710 "Not owning correct locks" 711 assert not self.owned_locks(locking.LEVEL_NODE) 712 713 es_flags = rpc.GetExclusiveStorageForNodes(self.cfg, 714 per_node_disks.keys()) 715 716 changed = [] 717 for node_uuid, dskl in per_node_disks.items(): 718 if not dskl: 719 # no disks on the node 720 continue 721 722 newl = [([v[2].Copy()], v[0]) for v in dskl] 723 node_name = self.cfg.GetNodeName(node_uuid) 724 result = self.rpc.call_blockdev_getdimensions(node_uuid, newl) 725 if result.fail_msg: 726 self.LogWarning("Failure in blockdev_getdimensions call to node" 727 " %s, ignoring", node_name) 728 continue 729 if len(result.payload) != len(dskl): 730 logging.warning("Invalid result from node %s: len(dksl)=%d," 731 " result.payload=%s", node_name, len(dskl), 732 result.payload) 733 self.LogWarning("Invalid result from node %s, ignoring node results", 734 node_name) 735 continue 736 for ((instance, idx, disk), dimensions) in zip(dskl, result.payload): 737 if dimensions is None: 738 self.LogWarning("Disk %d of instance %s did not return size" 739 " information, ignoring", idx, instance.name) 740 continue 741 if not isinstance(dimensions, (tuple, list)): 742 self.LogWarning("Disk %d of instance %s did not return valid" 743 " dimension information, ignoring", idx, 744 instance.name) 745 continue 746 (size, spindles) = dimensions 747 if not isinstance(size, (int, long)): 748 self.LogWarning("Disk %d of instance %s did not return valid" 749 " size information, ignoring", idx, instance.name) 750 continue 751 size = size >> 20 752 if size != disk.size: 753 self.LogInfo("Disk %d of instance %s has mismatched size," 754 " correcting: recorded %d, actual %d", idx, 755 instance.name, disk.size, size) 756 disk.size = size 757 self.cfg.Update(disk, feedback_fn) 758 changed.append((instance.name, idx, "size", size)) 759 if es_flags[node_uuid]: 760 if spindles is None: 761 self.LogWarning("Disk %d of instance %s did not return valid" 762 " spindles information, ignoring", idx, 763 instance.name) 764 elif disk.spindles is None or disk.spindles != spindles: 765 self.LogInfo("Disk %d of instance %s has mismatched spindles," 766 " correcting: recorded %s, actual %s", 767 idx, instance.name, disk.spindles, spindles) 768 disk.spindles = spindles 769 self.cfg.Update(disk, feedback_fn) 770 changed.append((instance.name, idx, "spindles", disk.spindles)) 771 if self._EnsureChildSizes(disk): 772 self.cfg.Update(disk, feedback_fn) 773 changed.append((instance.name, idx, "size", disk.size)) 774 return changed
775
776 777 -def _ValidateNetmask(cfg, netmask):
778 """Checks if a netmask is valid. 779 780 @type cfg: L{config.ConfigWriter} 781 @param cfg: cluster configuration 782 @type netmask: int 783 @param netmask: netmask to be verified 784 @raise errors.OpPrereqError: if the validation fails 785 786 """ 787 ip_family = cfg.GetPrimaryIPFamily() 788 try: 789 ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family) 790 except errors.ProgrammerError: 791 raise errors.OpPrereqError("Invalid primary ip family: %s." % 792 ip_family, errors.ECODE_INVAL) 793 if not ipcls.ValidateNetmask(netmask): 794 raise errors.OpPrereqError("CIDR netmask (%s) not valid" % 795 (netmask), errors.ECODE_INVAL)
796
797 798 -def CheckFileBasedStoragePathVsEnabledDiskTemplates( 799 logging_warn_fn, file_storage_dir, enabled_disk_templates, 800 file_disk_template):
801 """Checks whether the given file-based storage directory is acceptable. 802 803 Note: This function is public, because it is also used in bootstrap.py. 804 805 @type logging_warn_fn: function 806 @param logging_warn_fn: function which accepts a string and logs it 807 @type file_storage_dir: string 808 @param file_storage_dir: the directory to be used for file-based instances 809 @type enabled_disk_templates: list of string 810 @param enabled_disk_templates: the list of enabled disk templates 811 @type file_disk_template: string 812 @param file_disk_template: the file-based disk template for which the 813 path should be checked 814 815 """ 816 assert (file_disk_template in utils.storage.GetDiskTemplatesOfStorageTypes( 817 constants.ST_FILE, constants.ST_SHARED_FILE, constants.ST_GLUSTER 818 )) 819 820 file_storage_enabled = file_disk_template in enabled_disk_templates 821 if file_storage_dir is not None: 822 if file_storage_dir == "": 823 if file_storage_enabled: 824 raise errors.OpPrereqError( 825 "Unsetting the '%s' storage directory while having '%s' storage" 826 " enabled is not permitted." % 827 (file_disk_template, file_disk_template), 828 errors.ECODE_INVAL) 829 else: 830 if not file_storage_enabled: 831 logging_warn_fn( 832 "Specified a %s storage directory, although %s storage is not" 833 " enabled." % (file_disk_template, file_disk_template)) 834 else: 835 raise errors.ProgrammerError("Received %s storage dir with value" 836 " 'None'." % file_disk_template)
837
838 839 -def CheckFileStoragePathVsEnabledDiskTemplates( 840 logging_warn_fn, file_storage_dir, enabled_disk_templates):
841 """Checks whether the given file storage directory is acceptable. 842 843 @see: C{CheckFileBasedStoragePathVsEnabledDiskTemplates} 844 845 """ 846 CheckFileBasedStoragePathVsEnabledDiskTemplates( 847 logging_warn_fn, file_storage_dir, enabled_disk_templates, 848 constants.DT_FILE)
849
850 851 -def CheckSharedFileStoragePathVsEnabledDiskTemplates( 852 logging_warn_fn, file_storage_dir, enabled_disk_templates):
853 """Checks whether the given shared file storage directory is acceptable. 854 855 @see: C{CheckFileBasedStoragePathVsEnabledDiskTemplates} 856 857 """ 858 CheckFileBasedStoragePathVsEnabledDiskTemplates( 859 logging_warn_fn, file_storage_dir, enabled_disk_templates, 860 constants.DT_SHARED_FILE)
861
862 863 -def CheckGlusterStoragePathVsEnabledDiskTemplates( 864 logging_warn_fn, file_storage_dir, enabled_disk_templates):
865 """Checks whether the given gluster storage directory is acceptable. 866 867 @see: C{CheckFileBasedStoragePathVsEnabledDiskTemplates} 868 869 """ 870 CheckFileBasedStoragePathVsEnabledDiskTemplates( 871 logging_warn_fn, file_storage_dir, enabled_disk_templates, 872 constants.DT_GLUSTER)
873
874 875 -def CheckCompressionTools(tools):
876 """Check whether the provided compression tools look like executables. 877 878 @type tools: list of string 879 @param tools: The tools provided as opcode input 880 881 """ 882 regex = re.compile('^[-_a-zA-Z0-9]+$') 883 illegal_tools = [t for t in tools if not regex.match(t)] 884 885 if illegal_tools: 886 raise errors.OpPrereqError( 887 "The tools '%s' contain illegal characters: only alphanumeric values," 888 " dashes, and underscores are allowed" % ", ".join(illegal_tools), 889 errors.ECODE_INVAL 890 ) 891 892 if constants.IEC_GZIP not in tools: 893 raise errors.OpPrereqError("For compatibility reasons, the %s utility must" 894 " be present among the compression tools" % 895 constants.IEC_GZIP, errors.ECODE_INVAL) 896 897 if constants.IEC_NONE in tools: 898 raise errors.OpPrereqError("%s is a reserved value used for no compression," 899 " and cannot be used as the name of a tool" % 900 constants.IEC_NONE, errors.ECODE_INVAL)
901
902 903 -class LUClusterSetParams(LogicalUnit):
904 """Change the parameters of the cluster. 905 906 """ 907 HPATH = "cluster-modify" 908 HTYPE = constants.HTYPE_CLUSTER 909 REQ_BGL = False 910
911 - def CheckArguments(self):
912 """Check parameters 913 914 """ 915 if self.op.uid_pool: 916 uidpool.CheckUidPool(self.op.uid_pool) 917 918 if self.op.add_uids: 919 uidpool.CheckUidPool(self.op.add_uids) 920 921 if self.op.remove_uids: 922 uidpool.CheckUidPool(self.op.remove_uids) 923 924 if self.op.mac_prefix: 925 self.op.mac_prefix = \ 926 utils.NormalizeAndValidateThreeOctetMacPrefix(self.op.mac_prefix) 927 928 if self.op.master_netmask is not None: 929 _ValidateNetmask(self.cfg, self.op.master_netmask) 930 931 if self.op.diskparams: 932 for dt_params in self.op.diskparams.values(): 933 utils.ForceDictType(dt_params, constants.DISK_DT_TYPES) 934 try: 935 utils.VerifyDictOptions(self.op.diskparams, constants.DISK_DT_DEFAULTS) 936 CheckDiskAccessModeValidity(self.op.diskparams) 937 except errors.OpPrereqError, err: 938 raise errors.OpPrereqError("While verify diskparams options: %s" % err, 939 errors.ECODE_INVAL) 940 941 if self.op.install_image is not None: 942 CheckImageValidity(self.op.install_image, 943 "Install image must be an absolute path or a URL")
944
945 - def ExpandNames(self):
946 # FIXME: in the future maybe other cluster params won't require checking on 947 # all nodes to be modified. 948 # FIXME: This opcode changes cluster-wide settings. Is acquiring all 949 # resource locks the right thing, shouldn't it be the BGL instead? 950 self.needed_locks = { 951 locking.LEVEL_NODE: locking.ALL_SET, 952 locking.LEVEL_INSTANCE: locking.ALL_SET, 953 locking.LEVEL_NODEGROUP: locking.ALL_SET, 954 } 955 self.share_locks = ShareAll()
956
957 - def BuildHooksEnv(self):
958 """Build hooks env. 959 960 """ 961 return { 962 "OP_TARGET": self.cfg.GetClusterName(), 963 "NEW_VG_NAME": self.op.vg_name, 964 }
965
966 - def BuildHooksNodes(self):
967 """Build hooks nodes. 968 969 """ 970 mn = self.cfg.GetMasterNode() 971 return ([mn], [mn])
972
973 - def _CheckVgName(self, node_uuids, enabled_disk_templates, 974 new_enabled_disk_templates):
975 """Check the consistency of the vg name on all nodes and in case it gets 976 unset whether there are instances still using it. 977 978 """ 979 lvm_is_enabled = utils.IsLvmEnabled(enabled_disk_templates) 980 lvm_gets_enabled = utils.LvmGetsEnabled(enabled_disk_templates, 981 new_enabled_disk_templates) 982 current_vg_name = self.cfg.GetVGName() 983 984 if self.op.vg_name == '': 985 if lvm_is_enabled: 986 raise errors.OpPrereqError("Cannot unset volume group if lvm-based" 987 " disk templates are or get enabled.", 988 errors.ECODE_INVAL) 989 990 if self.op.vg_name is None: 991 if current_vg_name is None and lvm_is_enabled: 992 raise errors.OpPrereqError("Please specify a volume group when" 993 " enabling lvm-based disk-templates.", 994 errors.ECODE_INVAL) 995 996 if self.op.vg_name is not None and not self.op.vg_name: 997 if self.cfg.DisksOfType(constants.DT_PLAIN): 998 raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based" 999 " instances exist", errors.ECODE_INVAL) 1000 1001 if (self.op.vg_name is not None and lvm_is_enabled) or \ 1002 (self.cfg.GetVGName() is not None and lvm_gets_enabled): 1003 self._CheckVgNameOnNodes(node_uuids)
1004
1005 - def _CheckVgNameOnNodes(self, node_uuids):
1006 """Check the status of the volume group on each node. 1007 1008 """ 1009 vglist = self.rpc.call_vg_list(node_uuids) 1010 for node_uuid in node_uuids: 1011 msg = vglist[node_uuid].fail_msg 1012 if msg: 1013 # ignoring down node 1014 self.LogWarning("Error while gathering data on node %s" 1015 " (ignoring node): %s", 1016 self.cfg.GetNodeName(node_uuid), msg) 1017 continue 1018 vgstatus = utils.CheckVolumeGroupSize(vglist[node_uuid].payload, 1019 self.op.vg_name, 1020 constants.MIN_VG_SIZE) 1021 if vgstatus: 1022 raise errors.OpPrereqError("Error on node '%s': %s" % 1023 (self.cfg.GetNodeName(node_uuid), vgstatus), 1024 errors.ECODE_ENVIRON)
1025 1026 @staticmethod
1027 - def _GetDiskTemplateSetsInner(op_enabled_disk_templates, 1028 old_enabled_disk_templates):
1029 """Computes three sets of disk templates. 1030 1031 @see: C{_GetDiskTemplateSets} for more details. 1032 1033 """ 1034 enabled_disk_templates = None 1035 new_enabled_disk_templates = [] 1036 disabled_disk_templates = [] 1037 if op_enabled_disk_templates: 1038 enabled_disk_templates = op_enabled_disk_templates 1039 new_enabled_disk_templates = \ 1040 list(set(enabled_disk_templates) 1041 - set(old_enabled_disk_templates)) 1042 disabled_disk_templates = \ 1043 list(set(old_enabled_disk_templates) 1044 - set(enabled_disk_templates)) 1045 else: 1046 enabled_disk_templates = old_enabled_disk_templates 1047 return (enabled_disk_templates, new_enabled_disk_templates, 1048 disabled_disk_templates)
1049
1050 - def _GetDiskTemplateSets(self, cluster):
1051 """Computes three sets of disk templates. 1052 1053 The three sets are: 1054 - disk templates that will be enabled after this operation (no matter if 1055 they were enabled before or not) 1056 - disk templates that get enabled by this operation (thus haven't been 1057 enabled before.) 1058 - disk templates that get disabled by this operation 1059 1060 """ 1061 return self._GetDiskTemplateSetsInner(self.op.enabled_disk_templates, 1062 cluster.enabled_disk_templates)
1063
1064 - def _CheckIpolicy(self, cluster, enabled_disk_templates):
1065 """Checks the ipolicy. 1066 1067 @type cluster: C{objects.Cluster} 1068 @param cluster: the cluster's configuration 1069 @type enabled_disk_templates: list of string 1070 @param enabled_disk_templates: list of (possibly newly) enabled disk 1071 templates 1072 1073 """ 1074 # FIXME: write unit tests for this 1075 if self.op.ipolicy: 1076 self.new_ipolicy = GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy, 1077 group_policy=False) 1078 1079 CheckIpolicyVsDiskTemplates(self.new_ipolicy, 1080 enabled_disk_templates) 1081 1082 all_instances = self.cfg.GetAllInstancesInfo().values() 1083 violations = set() 1084 for group in self.cfg.GetAllNodeGroupsInfo().values(): 1085 instances = frozenset( 1086 [inst for inst in all_instances 1087 if compat.any(nuuid in group.members 1088 for nuuid in self.cfg.GetInstanceNodes(inst.uuid))]) 1089 new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy) 1090 ipol = masterd.instance.CalculateGroupIPolicy(cluster, group) 1091 new = ComputeNewInstanceViolations(ipol, new_ipolicy, instances, 1092 self.cfg) 1093 if new: 1094 violations.update(new) 1095 1096 if violations: 1097 self.LogWarning("After the ipolicy change the following instances" 1098 " violate them: %s", 1099 utils.CommaJoin(utils.NiceSort(violations))) 1100 else: 1101 CheckIpolicyVsDiskTemplates(cluster.ipolicy, 1102 enabled_disk_templates)
1103
1104 - def _CheckDrbdHelperOnNodes(self, drbd_helper, node_uuids):
1105 """Checks whether the set DRBD helper actually exists on the nodes. 1106 1107 @type drbd_helper: string 1108 @param drbd_helper: path of the drbd usermode helper binary 1109 @type node_uuids: list of strings 1110 @param node_uuids: list of node UUIDs to check for the helper 1111 1112 """ 1113 # checks given drbd helper on all nodes 1114 helpers = self.rpc.call_drbd_helper(node_uuids) 1115 for (_, ninfo) in self.cfg.GetMultiNodeInfo(node_uuids): 1116 if ninfo.offline: 1117 self.LogInfo("Not checking drbd helper on offline node %s", 1118 ninfo.name) 1119 continue 1120 msg = helpers[ninfo.uuid].fail_msg 1121 if msg: 1122 raise errors.OpPrereqError("Error checking drbd helper on node" 1123 " '%s': %s" % (ninfo.name, msg), 1124 errors.ECODE_ENVIRON) 1125 node_helper = helpers[ninfo.uuid].payload 1126 if node_helper != drbd_helper: 1127 raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" % 1128 (ninfo.name, node_helper), 1129 errors.ECODE_ENVIRON)
1130
1131 - def _CheckDrbdHelper(self, node_uuids, drbd_enabled, drbd_gets_enabled):
1132 """Check the DRBD usermode helper. 1133 1134 @type node_uuids: list of strings 1135 @param node_uuids: a list of nodes' UUIDs 1136 @type drbd_enabled: boolean 1137 @param drbd_enabled: whether DRBD will be enabled after this operation 1138 (no matter if it was disabled before or not) 1139 @type drbd_gets_enabled: boolen 1140 @param drbd_gets_enabled: true if DRBD was disabled before this 1141 operation, but will be enabled afterwards 1142 1143 """ 1144 if self.op.drbd_helper == '': 1145 if drbd_enabled: 1146 raise errors.OpPrereqError("Cannot disable drbd helper while" 1147 " DRBD is enabled.", errors.ECODE_STATE) 1148 if self.cfg.DisksOfType(constants.DT_DRBD8): 1149 raise errors.OpPrereqError("Cannot disable drbd helper while" 1150 " drbd-based instances exist", 1151 errors.ECODE_INVAL) 1152 1153 else: 1154 if self.op.drbd_helper is not None and drbd_enabled: 1155 self._CheckDrbdHelperOnNodes(self.op.drbd_helper, node_uuids) 1156 else: 1157 if drbd_gets_enabled: 1158 current_drbd_helper = self.cfg.GetClusterInfo().drbd_usermode_helper 1159 if current_drbd_helper is not None: 1160 self._CheckDrbdHelperOnNodes(current_drbd_helper, node_uuids) 1161 else: 1162 raise errors.OpPrereqError("Cannot enable DRBD without a" 1163 " DRBD usermode helper set.", 1164 errors.ECODE_STATE)
1165
1166 - def _CheckInstancesOfDisabledDiskTemplates( 1167 self, disabled_disk_templates):
1168 """Check whether we try to disable a disk template that is in use. 1169 1170 @type disabled_disk_templates: list of string 1171 @param disabled_disk_templates: list of disk templates that are going to 1172 be disabled by this operation 1173 1174 """ 1175 for disk_template in disabled_disk_templates: 1176 disks_with_type = self.cfg.DisksOfType(disk_template) 1177 if disks_with_type: 1178 disk_desc = [] 1179 for disk in disks_with_type: 1180 instance_uuid = self.cfg.GetInstanceForDisk(disk.uuid) 1181 instance = self.cfg.GetInstanceInfo(instance_uuid) 1182 if instance: 1183 instance_desc = "on " + instance.name 1184 else: 1185 instance_desc = "detached" 1186 disk_desc.append("%s (%s)" % (disk, instance_desc)) 1187 raise errors.OpPrereqError( 1188 "Cannot disable disk template '%s', because there is at least one" 1189 " disk using it:\n * %s" % (disk_template, "\n * ".join(disk_desc)), 1190 errors.ECODE_STATE) 1191 if constants.DT_DISKLESS in disabled_disk_templates: 1192 instances = self.cfg.GetAllInstancesInfo() 1193 for inst in instances.values(): 1194 if not inst.disks: 1195 raise errors.OpPrereqError( 1196 "Cannot disable disk template 'diskless', because there is at" 1197 " least one instance using it:\n * %s" % inst.name, 1198 errors.ECODE_STATE)
1199 1200 @staticmethod
1201 - def _CheckInstanceCommunicationNetwork(network, warning_fn):
1202 """Check whether an existing network is configured for instance 1203 communication. 1204 1205 Checks whether an existing network is configured with the 1206 parameters that are advisable for instance communication, and 1207 otherwise issue security warnings. 1208 1209 @type network: L{ganeti.objects.Network} 1210 @param network: L{ganeti.objects.Network} object whose 1211 configuration is being checked 1212 @type warning_fn: function 1213 @param warning_fn: function used to print warnings 1214 @rtype: None 1215 @return: None 1216 1217 """ 1218 def _MaybeWarn(err, val, default): 1219 if val != default: 1220 warning_fn("Supplied instance communication network '%s' %s '%s'," 1221 " this might pose a security risk (default is '%s').", 1222 network.name, err, val, default)
1223 1224 if network.network is None: 1225 raise errors.OpPrereqError("Supplied instance communication network '%s'" 1226 " must have an IPv4 network address.", 1227 network.name) 1228 1229 _MaybeWarn("has an IPv4 gateway", network.gateway, None) 1230 _MaybeWarn("has a non-standard IPv4 network address", network.network, 1231 constants.INSTANCE_COMMUNICATION_NETWORK4) 1232 _MaybeWarn("has an IPv6 gateway", network.gateway6, None) 1233 _MaybeWarn("has a non-standard IPv6 network address", network.network6, 1234 constants.INSTANCE_COMMUNICATION_NETWORK6) 1235 _MaybeWarn("has a non-standard MAC prefix", network.mac_prefix, 1236 constants.INSTANCE_COMMUNICATION_MAC_PREFIX)
1237
1238 - def CheckPrereq(self):
1239 """Check prerequisites. 1240 1241 This checks whether the given params don't conflict and 1242 if the given volume group is valid. 1243 1244 """ 1245 node_uuids = self.owned_locks(locking.LEVEL_NODE) 1246 self.cluster = cluster = self.cfg.GetClusterInfo() 1247 1248 vm_capable_node_uuids = [node.uuid 1249 for node in self.cfg.GetAllNodesInfo().values() 1250 if node.uuid in node_uuids and node.vm_capable] 1251 1252 (enabled_disk_templates, new_enabled_disk_templates, 1253 disabled_disk_templates) = self._GetDiskTemplateSets(cluster) 1254 self._CheckInstancesOfDisabledDiskTemplates(disabled_disk_templates) 1255 1256 self._CheckVgName(vm_capable_node_uuids, enabled_disk_templates, 1257 new_enabled_disk_templates) 1258 1259 if self.op.file_storage_dir is not None: 1260 CheckFileStoragePathVsEnabledDiskTemplates( 1261 self.LogWarning, self.op.file_storage_dir, enabled_disk_templates) 1262 1263 if self.op.shared_file_storage_dir is not None: 1264 CheckSharedFileStoragePathVsEnabledDiskTemplates( 1265 self.LogWarning, self.op.shared_file_storage_dir, 1266 enabled_disk_templates) 1267 1268 drbd_enabled = constants.DT_DRBD8 in enabled_disk_templates 1269 drbd_gets_enabled = constants.DT_DRBD8 in new_enabled_disk_templates 1270 self._CheckDrbdHelper(vm_capable_node_uuids, 1271 drbd_enabled, drbd_gets_enabled) 1272 1273 # validate params changes 1274 if self.op.beparams: 1275 objects.UpgradeBeParams(self.op.beparams) 1276 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES) 1277 self.new_beparams = cluster.SimpleFillBE(self.op.beparams) 1278 1279 if self.op.ndparams: 1280 utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES) 1281 self.new_ndparams = cluster.SimpleFillND(self.op.ndparams) 1282 1283 # TODO: we need a more general way to handle resetting 1284 # cluster-level parameters to default values 1285 if self.new_ndparams["oob_program"] == "": 1286 self.new_ndparams["oob_program"] = \ 1287 constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM] 1288 1289 if self.op.hv_state: 1290 new_hv_state = MergeAndVerifyHvState(self.op.hv_state, 1291 self.cluster.hv_state_static) 1292 self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values)) 1293 for hv, values in new_hv_state.items()) 1294 1295 if self.op.disk_state: 1296 new_disk_state = MergeAndVerifyDiskState(self.op.disk_state, 1297 self.cluster.disk_state_static) 1298 self.new_disk_state = \ 1299 dict((storage, dict((name, cluster.SimpleFillDiskState(values)) 1300 for name, values in svalues.items())) 1301 for storage, svalues in new_disk_state.items()) 1302 1303 self._CheckIpolicy(cluster, enabled_disk_templates) 1304 1305 if self.op.nicparams: 1306 utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES) 1307 self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams) 1308 objects.NIC.CheckParameterSyntax(self.new_nicparams) 1309 nic_errors = [] 1310 1311 # check all instances for consistency 1312 for instance in self.cfg.GetAllInstancesInfo().values(): 1313 for nic_idx, nic in enumerate(instance.nics): 1314 params_copy = copy.deepcopy(nic.nicparams) 1315 params_filled = objects.FillDict(self.new_nicparams, params_copy) 1316 1317 # check parameter syntax 1318 try: 1319 objects.NIC.CheckParameterSyntax(params_filled) 1320 except errors.ConfigurationError, err: 1321 nic_errors.append("Instance %s, nic/%d: %s" % 1322 (instance.name, nic_idx, err)) 1323 1324 # if we're moving instances to routed, check that they have an ip 1325 target_mode = params_filled[constants.NIC_MODE] 1326 if target_mode == constants.NIC_MODE_ROUTED and not nic.ip: 1327 nic_errors.append("Instance %s, nic/%d: routed NIC with no ip" 1328 " address" % (instance.name, nic_idx)) 1329 if nic_errors: 1330 raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" % 1331 "\n".join(nic_errors), errors.ECODE_INVAL) 1332 1333 # hypervisor list/parameters 1334 self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {}) 1335 if self.op.hvparams: 1336 for hv_name, hv_dict in self.op.hvparams.items(): 1337 if hv_name not in self.new_hvparams: 1338 self.new_hvparams[hv_name] = hv_dict 1339 else: 1340 self.new_hvparams[hv_name].update(hv_dict) 1341 1342 # disk template parameters 1343 self.new_diskparams = objects.FillDict(cluster.diskparams, {}) 1344 if self.op.diskparams: 1345 for dt_name, dt_params in self.op.diskparams.items(): 1346 if dt_name not in self.new_diskparams: 1347 self.new_diskparams[dt_name] = dt_params 1348 else: 1349 self.new_diskparams[dt_name].update(dt_params) 1350 CheckDiskAccessModeConsistency(self.op.diskparams, self.cfg) 1351 1352 # os hypervisor parameters 1353 self.new_os_hvp = objects.FillDict(cluster.os_hvp, {}) 1354 if self.op.os_hvp: 1355 for os_name, hvs in self.op.os_hvp.items(): 1356 if os_name not in self.new_os_hvp: 1357 self.new_os_hvp[os_name] = hvs 1358 else: 1359 for hv_name, hv_dict in hvs.items(): 1360 if hv_dict is None: 1361 # Delete if it exists 1362 self.new_os_hvp[os_name].pop(hv_name, None) 1363 elif hv_name not in self.new_os_hvp[os_name]: 1364 self.new_os_hvp[os_name][hv_name] = hv_dict 1365 else: 1366 self.new_os_hvp[os_name][hv_name].update(hv_dict) 1367 1368 # os parameters 1369 self._BuildOSParams(cluster) 1370 1371 # changes to the hypervisor list 1372 if self.op.enabled_hypervisors is not None: 1373 for hv in self.op.enabled_hypervisors: 1374 # if the hypervisor doesn't already exist in the cluster 1375 # hvparams, we initialize it to empty, and then (in both 1376 # cases) we make sure to fill the defaults, as we might not 1377 # have a complete defaults list if the hypervisor wasn't 1378 # enabled before 1379 if hv not in new_hvp: 1380 new_hvp[hv] = {} 1381 new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv]) 1382 utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES) 1383 1384 if self.op.hvparams or self.op.enabled_hypervisors is not None: 1385 # either the enabled list has changed, or the parameters have, validate 1386 for hv_name, hv_params in self.new_hvparams.items(): 1387 if ((self.op.hvparams and hv_name in self.op.hvparams) or 1388 (self.op.enabled_hypervisors and 1389 hv_name in self.op.enabled_hypervisors)): 1390 # either this is a new hypervisor, or its parameters have changed 1391 hv_class = hypervisor.GetHypervisorClass(hv_name) 1392 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES) 1393 hv_class.CheckParameterSyntax(hv_params) 1394 CheckHVParams(self, node_uuids, hv_name, hv_params) 1395 1396 if self.op.os_hvp: 1397 # no need to check any newly-enabled hypervisors, since the 1398 # defaults have already been checked in the above code-block 1399 for os_name, os_hvp in self.new_os_hvp.items(): 1400 for hv_name, hv_params in os_hvp.items(): 1401 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES) 1402 # we need to fill in the new os_hvp on top of the actual hv_p 1403 cluster_defaults = self.new_hvparams.get(hv_name, {}) 1404 new_osp = objects.FillDict(cluster_defaults, hv_params) 1405 hv_class = hypervisor.GetHypervisorClass(hv_name) 1406 hv_class.CheckParameterSyntax(new_osp) 1407 CheckHVParams(self, node_uuids, hv_name, new_osp) 1408 1409 if self.op.default_iallocator: 1410 alloc_script = utils.FindFile(self.op.default_iallocator, 1411 constants.IALLOCATOR_SEARCH_PATH, 1412 os.path.isfile) 1413 if alloc_script is None: 1414 raise errors.OpPrereqError("Invalid default iallocator script '%s'" 1415 " specified" % self.op.default_iallocator, 1416 errors.ECODE_INVAL) 1417 1418 if self.op.instance_communication_network: 1419 network_name = self.op.instance_communication_network 1420 1421 try: 1422 network_uuid = self.cfg.LookupNetwork(network_name) 1423 except errors.OpPrereqError: 1424 network_uuid = None 1425 1426 if network_uuid is not None: 1427 network = self.cfg.GetNetwork(network_uuid) 1428 self._CheckInstanceCommunicationNetwork(network, self.LogWarning) 1429 1430 if self.op.compression_tools: 1431 CheckCompressionTools(self.op.compression_tools)
1432
1433 - def _BuildOSParams(self, cluster):
1434 "Calculate the new OS parameters for this operation." 1435 1436 def _GetNewParams(source, new_params): 1437 "Wrapper around GetUpdatedParams." 1438 if new_params is None: 1439 return source 1440 result = objects.FillDict(source, {}) # deep copy of source 1441 for os_name in new_params: 1442 result[os_name] = GetUpdatedParams(result.get(os_name, {}), 1443 new_params[os_name], 1444 use_none=True) 1445 if not result[os_name]: 1446 del result[os_name] # we removed all parameters 1447 return result
1448 1449 self.new_osp = _GetNewParams(cluster.osparams, 1450 self.op.osparams) 1451 self.new_osp_private = _GetNewParams(cluster.osparams_private_cluster, 1452 self.op.osparams_private_cluster) 1453 1454 # Remove os validity check 1455 changed_oses = (set(self.new_osp.keys()) | set(self.new_osp_private.keys())) 1456 for os_name in changed_oses: 1457 os_params = cluster.SimpleFillOS( 1458 os_name, 1459 self.new_osp.get(os_name, {}), 1460 os_params_private=self.new_osp_private.get(os_name, {}) 1461 ) 1462 # check the parameter validity (remote check) 1463 CheckOSParams(self, False, [self.cfg.GetMasterNode()], 1464 os_name, os_params, False) 1465
1466 - def _SetVgName(self, feedback_fn):
1467 """Determines and sets the new volume group name. 1468 1469 """ 1470 if self.op.vg_name is not None: 1471 new_volume = self.op.vg_name 1472 if not new_volume: 1473 new_volume = None 1474 if new_volume != self.cfg.GetVGName(): 1475 self.cfg.SetVGName(new_volume) 1476 else: 1477 feedback_fn("Cluster LVM configuration already in desired" 1478 " state, not changing")
1479
1480 - def _SetFileStorageDir(self, feedback_fn):
1481 """Set the file storage directory. 1482 1483 """ 1484 if self.op.file_storage_dir is not None: 1485 if self.cluster.file_storage_dir == self.op.file_storage_dir: 1486 feedback_fn("Global file storage dir already set to value '%s'" 1487 % self.cluster.file_storage_dir) 1488 else: 1489 self.cluster.file_storage_dir = self.op.file_storage_dir
1490
1491 - def _SetSharedFileStorageDir(self, feedback_fn):
1492 """Set the shared file storage directory. 1493 1494 """ 1495 if self.op.shared_file_storage_dir is not None: 1496 if self.cluster.shared_file_storage_dir == \ 1497 self.op.shared_file_storage_dir: 1498 feedback_fn("Global shared file storage dir already set to value '%s'" 1499 % self.cluster.shared_file_storage_dir) 1500 else: 1501 self.cluster.shared_file_storage_dir = self.op.shared_file_storage_dir
1502
1503 - def _SetDrbdHelper(self, feedback_fn):
1504 """Set the DRBD usermode helper. 1505 1506 """ 1507 if self.op.drbd_helper is not None: 1508 if not constants.DT_DRBD8 in self.cluster.enabled_disk_templates: 1509 feedback_fn("Note that you specified a drbd user helper, but did not" 1510 " enable the drbd disk template.") 1511 new_helper = self.op.drbd_helper 1512 if not new_helper: 1513 new_helper = None 1514 if new_helper != self.cfg.GetDRBDHelper(): 1515 self.cfg.SetDRBDHelper(new_helper) 1516 else: 1517 feedback_fn("Cluster DRBD helper already in desired state," 1518 " not changing")
1519 1520 @staticmethod
1521 - def _EnsureInstanceCommunicationNetwork(cfg, network_name):
1522 """Ensure that the instance communication network exists and is 1523 connected to all groups. 1524 1525 The instance communication network given by L{network_name} it is 1526 created, if necessary, via the opcode 'OpNetworkAdd'. Also, the 1527 instance communication network is connected to all existing node 1528 groups, if necessary, via the opcode 'OpNetworkConnect'. 1529 1530 @type cfg: L{config.ConfigWriter} 1531 @param cfg: cluster configuration 1532 1533 @type network_name: string 1534 @param network_name: instance communication network name 1535 1536 @rtype: L{ganeti.cmdlib.ResultWithJobs} or L{None} 1537 @return: L{ganeti.cmdlib.ResultWithJobs} if the instance 1538 communication needs to be created or it needs to be 1539 connected to a group, otherwise L{None} 1540 1541 """ 1542 jobs = [] 1543 1544 try: 1545 network_uuid = cfg.LookupNetwork(network_name) 1546 network_exists = True 1547 except errors.OpPrereqError: 1548 network_exists = False 1549 1550 if not network_exists: 1551 jobs.append(AddInstanceCommunicationNetworkOp(network_name)) 1552 1553 for group_uuid in cfg.GetNodeGroupList(): 1554 group = cfg.GetNodeGroup(group_uuid) 1555 1556 if network_exists: 1557 network_connected = network_uuid in group.networks 1558 else: 1559 # The network was created asynchronously by the previous 1560 # opcode and, therefore, we don't have access to its 1561 # network_uuid. As a result, we assume that the network is 1562 # not connected to any group yet. 1563 network_connected = False 1564 1565 if not network_connected: 1566 op = ConnectInstanceCommunicationNetworkOp(group_uuid, network_name) 1567 jobs.append(op) 1568 1569 if jobs: 1570 return ResultWithJobs([jobs]) 1571 else: 1572 return None
1573 1574 @staticmethod
1575 - def _ModifyInstanceCommunicationNetwork(cfg, network_name, feedback_fn):
1576 """Update the instance communication network stored in the cluster 1577 configuration. 1578 1579 Compares the user-supplied instance communication network against 1580 the one stored in the Ganeti cluster configuration. If there is a 1581 change, the instance communication network may be possibly created 1582 and connected to all groups (see 1583 L{LUClusterSetParams._EnsureInstanceCommunicationNetwork}). 1584 1585 @type cfg: L{config.ConfigWriter} 1586 @param cfg: cluster configuration 1587 1588 @type network_name: string 1589 @param network_name: instance communication network name 1590 1591 @type feedback_fn: function 1592 @param feedback_fn: see L{ganeti.cmdlist.base.LogicalUnit} 1593 1594 @rtype: L{LUClusterSetParams._EnsureInstanceCommunicationNetwork} or L{None} 1595 @return: see L{LUClusterSetParams._EnsureInstanceCommunicationNetwork} 1596 1597 """ 1598 config_network_name = cfg.GetInstanceCommunicationNetwork() 1599 1600 if network_name == config_network_name: 1601 feedback_fn("Instance communication network already is '%s', nothing to" 1602 " do." % network_name) 1603 else: 1604 try: 1605 cfg.LookupNetwork(config_network_name) 1606 feedback_fn("Previous instance communication network '%s'" 1607 " should be removed manually." % config_network_name) 1608 except errors.OpPrereqError: 1609 pass 1610 1611 if network_name: 1612 feedback_fn("Changing instance communication network to '%s', only new" 1613 " instances will be affected." 1614 % network_name) 1615 else: 1616 feedback_fn("Disabling instance communication network, only new" 1617 " instances will be affected.") 1618 1619 cfg.SetInstanceCommunicationNetwork(network_name) 1620 1621 if network_name: 1622 return LUClusterSetParams._EnsureInstanceCommunicationNetwork( 1623 cfg, 1624 network_name) 1625 else: 1626 return None
1627
1628 - def Exec(self, feedback_fn):
1629 """Change the parameters of the cluster. 1630 1631 """ 1632 # re-read the fresh configuration 1633 self.cluster = self.cfg.GetClusterInfo() 1634 if self.op.enabled_disk_templates: 1635 self.cluster.enabled_disk_templates = \ 1636 list(self.op.enabled_disk_templates) 1637 # save the changes 1638 self.cfg.Update(self.cluster, feedback_fn) 1639 1640 self._SetVgName(feedback_fn) 1641 1642 self.cluster = self.cfg.GetClusterInfo() 1643 self._SetFileStorageDir(feedback_fn) 1644 self._SetSharedFileStorageDir(feedback_fn) 1645 self.cfg.Update(self.cluster, feedback_fn) 1646 self._SetDrbdHelper(feedback_fn) 1647 1648 # re-read the fresh configuration again 1649 self.cluster = self.cfg.GetClusterInfo() 1650 1651 ensure_kvmd = False 1652 1653 active = constants.DATA_COLLECTOR_STATE_ACTIVE 1654 if self.op.enabled_data_collectors is not None: 1655 for name, val in self.op.enabled_data_collectors.items(): 1656 self.cluster.data_collectors[name][active] = val 1657 1658 if self.op.data_collector_interval: 1659 internal = constants.DATA_COLLECTOR_PARAMETER_INTERVAL 1660 for name, val in self.op.data_collector_interval.items(): 1661 self.cluster.data_collectors[name][internal] = int(val) 1662 1663 if self.op.hvparams: 1664 self.cluster.hvparams = self.new_hvparams 1665 if self.op.os_hvp: 1666 self.cluster.os_hvp = self.new_os_hvp 1667 if self.op.enabled_hypervisors is not None: 1668 self.cluster.hvparams = self.new_hvparams 1669 self.cluster.enabled_hypervisors = self.op.enabled_hypervisors 1670 ensure_kvmd = True 1671 if self.op.beparams: 1672 self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams 1673 if self.op.nicparams: 1674 self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams 1675 if self.op.ipolicy: 1676 self.cluster.ipolicy = self.new_ipolicy 1677 if self.op.osparams: 1678 self.cluster.osparams = self.new_osp 1679 if self.op.osparams_private_cluster: 1680 self.cluster.osparams_private_cluster = self.new_osp_private 1681 if self.op.ndparams: 1682 self.cluster.ndparams = self.new_ndparams 1683 if self.op.diskparams: 1684 self.cluster.diskparams = self.new_diskparams 1685 if self.op.hv_state: 1686 self.cluster.hv_state_static = self.new_hv_state 1687 if self.op.disk_state: 1688 self.cluster.disk_state_static = self.new_disk_state 1689 1690 if self.op.candidate_pool_size is not None: 1691 self.cluster.candidate_pool_size = self.op.candidate_pool_size 1692 # we need to update the pool size here, otherwise the save will fail 1693 master_node = self.cfg.GetMasterNode() 1694 potential_master_candidates = self.cfg.GetPotentialMasterCandidates() 1695 modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup 1696 AdjustCandidatePool( 1697 self, [], master_node, potential_master_candidates, feedback_fn, 1698 modify_ssh_setup) 1699 1700 if self.op.max_running_jobs is not None: 1701 self.cluster.max_running_jobs = self.op.max_running_jobs 1702 1703 if self.op.max_tracked_jobs is not None: 1704 self.cluster.max_tracked_jobs = self.op.max_tracked_jobs 1705 1706 if self.op.maintain_node_health is not None: 1707 self.cluster.maintain_node_health = self.op.maintain_node_health 1708 1709 if self.op.modify_etc_hosts is not None: 1710 self.cluster.modify_etc_hosts = self.op.modify_etc_hosts 1711 1712 if self.op.prealloc_wipe_disks is not None: 1713 self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks 1714 1715 if self.op.add_uids is not None: 1716 uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids) 1717 1718 if self.op.remove_uids is not None: 1719 uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids) 1720 1721 if self.op.uid_pool is not None: 1722 self.cluster.uid_pool = self.op.uid_pool 1723 1724 if self.op.default_iallocator is not None: 1725 self.cluster.default_iallocator = self.op.default_iallocator 1726 1727 if self.op.default_iallocator_params is not None: 1728 self.cluster.default_iallocator_params = self.op.default_iallocator_params 1729 1730 if self.op.reserved_lvs is not None: 1731 self.cluster.reserved_lvs = self.op.reserved_lvs 1732 1733 if self.op.use_external_mip_script is not None: 1734 self.cluster.use_external_mip_script = self.op.use_external_mip_script 1735 1736 if self.op.enabled_user_shutdown is not None and \ 1737 self.cluster.enabled_user_shutdown != self.op.enabled_user_shutdown: 1738 self.cluster.enabled_user_shutdown = self.op.enabled_user_shutdown 1739 ensure_kvmd = True 1740 1741 def helper_os(aname, mods, desc): 1742 desc += " OS list" 1743 lst = getattr(self.cluster, aname) 1744 for key, val in mods: 1745 if key == constants.DDM_ADD: 1746 if val in lst: 1747 feedback_fn("OS %s already in %s, ignoring" % (val, desc)) 1748 else: 1749 lst.append(val) 1750 elif key == constants.DDM_REMOVE: 1751 if val in lst: 1752 lst.remove(val) 1753 else: 1754 feedback_fn("OS %s not found in %s, ignoring" % (val, desc)) 1755 else: 1756 raise errors.ProgrammerError("Invalid modification '%s'" % key)
1757 1758 if self.op.hidden_os: 1759 helper_os("hidden_os", self.op.hidden_os, "hidden") 1760 1761 if self.op.blacklisted_os: 1762 helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted") 1763 1764 if self.op.mac_prefix: 1765 self.cluster.mac_prefix = self.op.mac_prefix 1766 1767 if self.op.master_netdev: 1768 master_params = self.cfg.GetMasterNetworkParameters() 1769 ems = self.cfg.GetUseExternalMipScript() 1770 feedback_fn("Shutting down master ip on the current netdev (%s)" % 1771 self.cluster.master_netdev) 1772 result = self.rpc.call_node_deactivate_master_ip(master_params.uuid, 1773 master_params, ems) 1774 if not self.op.force: 1775 result.Raise("Could not disable the master ip") 1776 else: 1777 if result.fail_msg: 1778 msg = ("Could not disable the master ip (continuing anyway): %s" % 1779 result.fail_msg) 1780 feedback_fn(msg) 1781 feedback_fn("Changing master_netdev from %s to %s" % 1782 (master_params.netdev, self.op.master_netdev)) 1783 self.cluster.master_netdev = self.op.master_netdev 1784 1785 if self.op.master_netmask: 1786 master_params = self.cfg.GetMasterNetworkParameters() 1787 feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask) 1788 result = self.rpc.call_node_change_master_netmask( 1789 master_params.uuid, master_params.netmask, 1790 self.op.master_netmask, master_params.ip, 1791 master_params.netdev) 1792 result.Warn("Could not change the master IP netmask", feedback_fn) 1793 self.cluster.master_netmask = self.op.master_netmask 1794 1795 if self.op.install_image: 1796 self.cluster.install_image = self.op.install_image 1797 1798 if self.op.zeroing_image is not None: 1799 CheckImageValidity(self.op.zeroing_image, 1800 "Zeroing image must be an absolute path or a URL") 1801 self.cluster.zeroing_image = self.op.zeroing_image 1802 1803 self.cfg.Update(self.cluster, feedback_fn) 1804 1805 if self.op.master_netdev: 1806 master_params = self.cfg.GetMasterNetworkParameters() 1807 feedback_fn("Starting the master ip on the new master netdev (%s)" % 1808 self.op.master_netdev) 1809 ems = self.cfg.GetUseExternalMipScript() 1810 result = self.rpc.call_node_activate_master_ip(master_params.uuid, 1811 master_params, ems) 1812 result.Warn("Could not re-enable the master ip on the master," 1813 " please restart manually", self.LogWarning) 1814 1815 # Even though 'self.op.enabled_user_shutdown' is being tested 1816 # above, the RPCs can only be done after 'self.cfg.Update' because 1817 # this will update the cluster object and sync 'Ssconf', and kvmd 1818 # uses 'Ssconf'. 1819 if ensure_kvmd: 1820 EnsureKvmdOnNodes(self, feedback_fn) 1821 1822 if self.op.compression_tools is not None: 1823 self.cfg.SetCompressionTools(self.op.compression_tools) 1824 1825 network_name = self.op.instance_communication_network 1826 if network_name is not None: 1827 return self._ModifyInstanceCommunicationNetwork(self.cfg, 1828 network_name, feedback_fn) 1829 else: 1830 return None 1831