Fail early for invalid key type and size combinations
[ganeti-github.git] / lib / cmdlib / cluster / __init__.py
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 AdjustCandidatePool(self, [])
1694
1695 if self.op.max_running_jobs is not None:
1696 self.cluster.max_running_jobs = self.op.max_running_jobs
1697
1698 if self.op.max_tracked_jobs is not None:
1699 self.cluster.max_tracked_jobs = self.op.max_tracked_jobs
1700
1701 if self.op.maintain_node_health is not None:
1702 self.cluster.maintain_node_health = self.op.maintain_node_health
1703
1704 if self.op.modify_etc_hosts is not None:
1705 self.cluster.modify_etc_hosts = self.op.modify_etc_hosts
1706
1707 if self.op.prealloc_wipe_disks is not None:
1708 self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
1709
1710 if self.op.add_uids is not None:
1711 uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
1712
1713 if self.op.remove_uids is not None:
1714 uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
1715
1716 if self.op.uid_pool is not None:
1717 self.cluster.uid_pool = self.op.uid_pool
1718
1719 if self.op.default_iallocator is not None:
1720 self.cluster.default_iallocator = self.op.default_iallocator
1721
1722 if self.op.default_iallocator_params is not None:
1723 self.cluster.default_iallocator_params = self.op.default_iallocator_params
1724
1725 if self.op.reserved_lvs is not None:
1726 self.cluster.reserved_lvs = self.op.reserved_lvs
1727
1728 if self.op.use_external_mip_script is not None:
1729 self.cluster.use_external_mip_script = self.op.use_external_mip_script
1730
1731 if self.op.enabled_user_shutdown is not None and \
1732 self.cluster.enabled_user_shutdown != self.op.enabled_user_shutdown:
1733 self.cluster.enabled_user_shutdown = self.op.enabled_user_shutdown
1734 ensure_kvmd = True
1735
1736 def helper_os(aname, mods, desc):
1737 desc += " OS list"
1738 lst = getattr(self.cluster, aname)
1739 for key, val in mods:
1740 if key == constants.DDM_ADD:
1741 if val in lst:
1742 feedback_fn("OS %s already in %s, ignoring" % (val, desc))
1743 else:
1744 lst.append(val)
1745 elif key == constants.DDM_REMOVE:
1746 if val in lst:
1747 lst.remove(val)
1748 else:
1749 feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
1750 else:
1751 raise errors.ProgrammerError("Invalid modification '%s'" % key)
1752
1753 if self.op.hidden_os:
1754 helper_os("hidden_os", self.op.hidden_os, "hidden")
1755
1756 if self.op.blacklisted_os:
1757 helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
1758
1759 if self.op.mac_prefix:
1760 self.cluster.mac_prefix = self.op.mac_prefix
1761
1762 if self.op.master_netdev:
1763 master_params = self.cfg.GetMasterNetworkParameters()
1764 ems = self.cfg.GetUseExternalMipScript()
1765 feedback_fn("Shutting down master ip on the current netdev (%s)" %
1766 self.cluster.master_netdev)
1767 result = self.rpc.call_node_deactivate_master_ip(master_params.uuid,
1768 master_params, ems)
1769 if not self.op.force:
1770 result.Raise("Could not disable the master ip")
1771 else:
1772 if result.fail_msg:
1773 msg = ("Could not disable the master ip (continuing anyway): %s" %
1774 result.fail_msg)
1775 feedback_fn(msg)
1776 feedback_fn("Changing master_netdev from %s to %s" %
1777 (master_params.netdev, self.op.master_netdev))
1778 self.cluster.master_netdev = self.op.master_netdev
1779
1780 if self.op.master_netmask:
1781 master_params = self.cfg.GetMasterNetworkParameters()
1782 feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
1783 result = self.rpc.call_node_change_master_netmask(
1784 master_params.uuid, master_params.netmask,
1785 self.op.master_netmask, master_params.ip,
1786 master_params.netdev)
1787 result.Warn("Could not change the master IP netmask", feedback_fn)
1788 self.cluster.master_netmask = self.op.master_netmask
1789
1790 if self.op.install_image:
1791 self.cluster.install_image = self.op.install_image
1792
1793 if self.op.zeroing_image is not None:
1794 CheckImageValidity(self.op.zeroing_image,
1795 "Zeroing image must be an absolute path or a URL")
1796 self.cluster.zeroing_image = self.op.zeroing_image
1797
1798 self.cfg.Update(self.cluster, feedback_fn)
1799
1800 if self.op.master_netdev:
1801 master_params = self.cfg.GetMasterNetworkParameters()
1802 feedback_fn("Starting the master ip on the new master netdev (%s)" %
1803 self.op.master_netdev)
1804 ems = self.cfg.GetUseExternalMipScript()
1805 result = self.rpc.call_node_activate_master_ip(master_params.uuid,
1806 master_params, ems)
1807 result.Warn("Could not re-enable the master ip on the master,"
1808 " please restart manually", self.LogWarning)
1809
1810 # Even though 'self.op.enabled_user_shutdown' is being tested
1811 # above, the RPCs can only be done after 'self.cfg.Update' because
1812 # this will update the cluster object and sync 'Ssconf', and kvmd
1813 # uses 'Ssconf'.
1814 if ensure_kvmd:
1815 EnsureKvmdOnNodes(self, feedback_fn)
1816
1817 if self.op.compression_tools is not None:
1818 self.cfg.SetCompressionTools(self.op.compression_tools)
1819
1820 network_name = self.op.instance_communication_network
1821 if network_name is not None:
1822 return self._ModifyInstanceCommunicationNetwork(self.cfg,
1823 network_name, feedback_fn)
1824 else:
1825 return None