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