4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
30 from ganeti.cli import *
31 from ganeti import opcodes
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti import bootstrap
36 from ganeti import ssh
37 from ganeti import objects
41 def InitCluster(opts, args):
42 """Initialize the cluster.
44 @param opts: the command line options selected by the user
46 @param args: should contain only one element, the desired
49 @return: the desired exit code
52 if not opts.lvm_storage and opts.vg_name:
53 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
56 vg_name = opts.vg_name
57 if opts.lvm_storage and not opts.vg_name:
58 vg_name = constants.DEFAULT_VG
60 hvlist = opts.enabled_hypervisors
62 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
63 hvlist = hvlist.split(",")
65 hvparams = dict(opts.hvparams)
66 beparams = opts.beparams
67 nicparams = opts.nicparams
69 # prepare beparams dict
70 beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
71 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
73 # prepare nicparams dict
74 nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
75 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
77 # prepare hvparams dict
78 for hv in constants.HYPER_TYPES:
79 if hv not in hvparams:
81 hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
82 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
84 if opts.candidate_pool_size is None:
85 opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
87 bootstrap.InitCluster(cluster_name=args[0],
88 secondary_ip=opts.secondary_ip,
90 mac_prefix=opts.mac_prefix,
91 master_netdev=opts.master_netdev,
92 file_storage_dir=opts.file_storage_dir,
93 enabled_hypervisors=hvlist,
97 candidate_pool_size=opts.candidate_pool_size,
98 modify_etc_hosts=opts.modify_etc_hosts,
100 op = opcodes.OpPostInitCluster()
106 def DestroyCluster(opts, args):
107 """Destroy the cluster.
109 @param opts: the command line options selected by the user
111 @param args: should be an empty list
113 @return: the desired exit code
116 if not opts.yes_do_it:
117 ToStderr("Destroying a cluster is irreversible. If you really want"
118 " destroy this cluster, supply the --yes-do-it option.")
121 op = opcodes.OpDestroyCluster()
122 master = SubmitOpCode(op)
123 # if we reached this, the opcode didn't fail; we can proceed to
124 # shutdown all the daemons
125 bootstrap.FinalizeClusterDestroy(master)
129 def RenameCluster(opts, args):
130 """Rename the cluster.
132 @param opts: the command line options selected by the user
134 @param args: should contain only one element, the new cluster name
136 @return: the desired exit code
141 usertext = ("This will rename the cluster to '%s'. If you are connected"
142 " over the network to the cluster name, the operation is very"
143 " dangerous as the IP address will be removed from the node"
144 " and the change may not go through. Continue?") % name
145 if not AskUser(usertext):
148 op = opcodes.OpRenameCluster(name=name)
153 def RedistributeConfig(opts, args):
154 """Forces push of the cluster configuration.
156 @param opts: the command line options selected by the user
158 @param args: empty list
160 @return: the desired exit code
163 op = opcodes.OpRedistributeConfig()
164 SubmitOrSend(op, opts)
168 def ShowClusterVersion(opts, args):
169 """Write version of ganeti software to the standard output.
171 @param opts: the command line options selected by the user
173 @param args: should be an empty list
175 @return: the desired exit code
179 result = cl.QueryClusterInfo()
180 ToStdout("Software version: %s", result["software_version"])
181 ToStdout("Internode protocol: %s", result["protocol_version"])
182 ToStdout("Configuration format: %s", result["config_version"])
183 ToStdout("OS api version: %s", result["os_api_version"])
184 ToStdout("Export interface: %s", result["export_version"])
188 def ShowClusterMaster(opts, args):
189 """Write name of master node to the standard output.
191 @param opts: the command line options selected by the user
193 @param args: should be an empty list
195 @return: the desired exit code
198 master = bootstrap.GetMaster()
202 def _PrintGroupedParams(paramsdict):
203 """Print Grouped parameters (be, nic, disk) by group.
205 @type paramsdict: dict of dicts
206 @param paramsdict: {group: {param: value, ...}, ...}
209 for gr_name, gr_dict in paramsdict.items():
210 ToStdout(" - %s:", gr_name)
211 for item, val in gr_dict.iteritems():
212 ToStdout(" %s: %s", item, val)
214 def ShowClusterConfig(opts, args):
215 """Shows cluster information.
217 @param opts: the command line options selected by the user
219 @param args: should be an empty list
221 @return: the desired exit code
225 result = cl.QueryClusterInfo()
227 ToStdout("Cluster name: %s", result["name"])
229 ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
230 ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
232 ToStdout("Master node: %s", result["master"])
234 ToStdout("Architecture (this node): %s (%s)",
235 result["architecture"][0], result["architecture"][1])
238 tags = ", ".join(utils.NiceSort(result["tags"]))
242 ToStdout("Tags: %s", tags)
244 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
245 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
247 ToStdout("Hypervisor parameters:")
248 _PrintGroupedParams(result["hvparams"])
250 ToStdout("Cluster parameters:")
251 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
252 ToStdout(" - master netdev: %s", result["master_netdev"])
253 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
254 ToStdout(" - file storage path: %s", result["file_storage_dir"])
256 ToStdout("Default instance parameters:")
257 _PrintGroupedParams(result["beparams"])
259 ToStdout("Default nic parameters:")
260 _PrintGroupedParams(result["nicparams"])
265 def ClusterCopyFile(opts, args):
266 """Copy a file from master to some nodes.
268 @param opts: the command line options selected by the user
270 @param args: should contain only one element, the path of
271 the file to be copied
273 @return: the desired exit code
277 if not os.path.exists(filename):
278 raise errors.OpPrereqError("No such filename '%s'" % filename)
282 myname = utils.HostInfo().name
284 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
286 results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
287 results = [name for name in results if name != myname]
289 srun = ssh.SshRunner(cluster_name=cluster_name)
291 if not srun.CopyFileToNode(node, filename):
292 ToStderr("Copy of file %s to node %s failed", filename, node)
297 def RunClusterCommand(opts, args):
298 """Run a command on some nodes.
300 @param opts: the command line options selected by the user
302 @param args: should contain the command to be run and its arguments
304 @return: the desired exit code
309 command = " ".join(args)
311 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
313 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
316 srun = ssh.SshRunner(cluster_name=cluster_name)
318 # Make sure master node is at list end
319 if master_node in nodes:
320 nodes.remove(master_node)
321 nodes.append(master_node)
324 result = srun.Run(name, "root", command)
325 ToStdout("------------------------------------------------")
326 ToStdout("node: %s", name)
327 ToStdout("%s", result.output)
328 ToStdout("return code = %s", result.exit_code)
333 def VerifyCluster(opts, args):
334 """Verify integrity of cluster, performing various test on nodes.
336 @param opts: the command line options selected by the user
338 @param args: should be an empty list
340 @return: the desired exit code
344 if opts.skip_nplusone_mem:
345 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
346 op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
347 verbose=opts.verbose,
348 error_codes=opts.error_codes,
349 debug_simulate_errors=opts.simulate_errors)
356 def VerifyDisks(opts, args):
357 """Verify integrity of cluster disks.
359 @param opts: the command line options selected by the user
361 @param args: should be an empty list
363 @return: the desired exit code
366 op = opcodes.OpVerifyDisks()
367 result = SubmitOpCode(op)
368 if not isinstance(result, (list, tuple)) or len(result) != 3:
369 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
371 bad_nodes, instances, missing = result
373 retcode = constants.EXIT_SUCCESS
376 for node, text in bad_nodes.items():
377 ToStdout("Error gathering data on node %s: %s",
378 node, utils.SafeEncode(text[-400:]))
380 ToStdout("You need to fix these nodes first before fixing instances")
383 for iname in instances:
386 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
388 ToStdout("Activating disks for instance '%s'", iname)
390 except errors.GenericError, err:
391 nret, msg = FormatError(err)
393 ToStderr("Error activating disks for instance %s: %s", iname, msg)
396 for iname, ival in missing.iteritems():
397 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
399 ToStdout("Instance %s cannot be verified as it lives on"
400 " broken nodes", iname)
402 ToStdout("Instance %s has missing logical volumes:", iname)
404 for node, vol in ival:
405 if node in bad_nodes:
406 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
408 ToStdout("\t%s /dev/xenvg/%s", node, vol)
409 ToStdout("You need to run replace_disks for all the above"
410 " instances, if this message persist after fixing nodes.")
416 def RepairDiskSizes(opts, args):
417 """Verify sizes of cluster disks.
419 @param opts: the command line options selected by the user
421 @param args: optional list of instances to restrict check to
423 @return: the desired exit code
426 op = opcodes.OpRepairDiskSizes(instances=args)
431 def MasterFailover(opts, args):
432 """Failover the master node.
434 This command, when run on a non-master node, will cause the current
435 master to cease being master, and the non-master to become new
438 @param opts: the command line options selected by the user
440 @param args: should be an empty list
442 @return: the desired exit code
446 usertext = ("This will perform the failover even if most other nodes"
447 " are down, or if this node is outdated. This is dangerous"
448 " as it can lead to a non-consistent cluster. Check the"
449 " gnt-cluster(8) man page before proceeding. Continue?")
450 if not AskUser(usertext):
453 return bootstrap.MasterFailover(no_voting=opts.no_voting)
456 def SearchTags(opts, args):
457 """Searches the tags on all the cluster.
459 @param opts: the command line options selected by the user
461 @param args: should contain only one element, the tag pattern
463 @return: the desired exit code
466 op = opcodes.OpSearchTags(pattern=args[0])
467 result = SubmitOpCode(op)
470 result = list(result)
472 for path, tag in result:
473 ToStdout("%s %s", path, tag)
476 def SetClusterParams(opts, args):
477 """Modify the cluster.
479 @param opts: the command line options selected by the user
481 @param args: should be an empty list
483 @return: the desired exit code
486 if not (not opts.lvm_storage or opts.vg_name or
487 opts.enabled_hypervisors or opts.hvparams or
488 opts.beparams or opts.nicparams or
489 opts.candidate_pool_size is not None):
490 ToStderr("Please give at least one of the parameters.")
493 vg_name = opts.vg_name
494 if not opts.lvm_storage and opts.vg_name:
495 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
497 elif not opts.lvm_storage:
500 hvlist = opts.enabled_hypervisors
501 if hvlist is not None:
502 hvlist = hvlist.split(",")
504 # a list of (name, dict) we can pass directly to dict() (or [])
505 hvparams = dict(opts.hvparams)
506 for hv, hv_params in hvparams.iteritems():
507 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
509 beparams = opts.beparams
510 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
512 nicparams = opts.nicparams
513 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
515 op = opcodes.OpSetClusterParams(vg_name=vg_name,
516 enabled_hypervisors=hvlist,
520 candidate_pool_size=opts.candidate_pool_size)
525 def QueueOps(opts, args):
528 @param opts: the command line options selected by the user
530 @param args: should contain only one element, the subcommand
532 @return: the desired exit code
537 if command in ("drain", "undrain"):
538 drain_flag = command == "drain"
539 client.SetQueueDrainFlag(drain_flag)
540 elif command == "info":
541 result = client.QueryConfigValues(["drain_flag"])
546 ToStdout("The drain flag is %s" % val)
548 raise errors.OpPrereqError("Command '%s' is not valid." % command)
553 def _ShowWatcherPause(until):
554 if until is None or until < time.time():
555 ToStdout("The watcher is not paused.")
557 ToStdout("The watcher is paused until %s.", time.ctime(until))
560 def WatcherOps(opts, args):
561 """Watcher operations.
563 @param opts: the command line options selected by the user
565 @param args: should contain only one element, the subcommand
567 @return: the desired exit code
573 if command == "continue":
574 client.SetWatcherPause(None)
575 ToStdout("The watcher is no longer paused.")
577 elif command == "pause":
579 raise errors.OpPrereqError("Missing pause duration")
581 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
582 _ShowWatcherPause(result)
584 elif command == "info":
585 result = client.QueryConfigValues(["watcher_pause"])
586 _ShowWatcherPause(result)
589 raise errors.OpPrereqError("Command '%s' is not valid." % command)
595 'init': (InitCluster, [ArgHost(min=1, max=1)],
598 cli_option("-m", "--mac-prefix", dest="mac_prefix",
599 help="Specify the mac prefix for the instance IP"
600 " addresses, in the format XX:XX:XX",
602 default=constants.DEFAULT_MAC_PREFIX,),
604 cli_option("--master-netdev", dest="master_netdev",
605 help="Specify the node interface (cluster-wide)"
606 " on which the master IP address will be added "
607 " [%s]" % constants.DEFAULT_BRIDGE,
609 default=constants.DEFAULT_BRIDGE,),
610 cli_option("--file-storage-dir", dest="file_storage_dir",
611 help="Specify the default directory (cluster-wide)"
612 " for storing the file-based disks [%s]" %
613 constants.DEFAULT_FILE_STORAGE_DIR,
615 default=constants.DEFAULT_FILE_STORAGE_DIR,),
617 cli_option("--no-etc-hosts", dest="modify_etc_hosts",
618 help="Don't modify /etc/hosts"
620 action="store_false", default=True,),
627 "[opts...] <cluster_name>",
628 "Initialises a new cluster configuration"),
629 'destroy': (DestroyCluster, ARGS_NONE,
631 cli_option("--yes-do-it", dest="yes_do_it",
632 help="Destroy cluster",
633 action="store_true"),
635 "", "Destroy cluster"),
636 'rename': (RenameCluster, [ArgHost(min=1, max=1)],
637 [DEBUG_OPT, FORCE_OPT],
639 "Renames the cluster"),
640 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
642 "Forces a push of the configuration file and ssconf files"
643 " to the nodes in the cluster"),
644 'verify': (VerifyCluster, ARGS_NONE,
645 [DEBUG_OPT, VERBOSE_OPT, DEBUG_SIMERR_OPT,
646 cli_option("--error-codes", dest="error_codes",
647 help="Enable parseable error messages",
648 action="store_true", default=False),
649 cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
650 help="Skip N+1 memory redundancy tests",
651 action="store_true", default=False),
653 "", "Does a check on the cluster configuration"),
654 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
655 "", "Does a check on the cluster disk status"),
656 'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
657 "", "Updates mismatches in recorded disk sizes"),
658 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
659 cli_option("--no-voting", dest="no_voting",
660 help="Skip node agreement check (dangerous)",
664 "", "Makes the current node the master"),
665 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
666 "", "Shows the cluster version"),
667 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
668 "", "Shows the cluster master"),
669 'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
670 [DEBUG_OPT, NODE_LIST_OPT],
671 "[-n node...] <filename>",
672 "Copies a file to all (or only some) nodes"),
673 'command': (RunClusterCommand, [ArgCommand(min=1)],
674 [DEBUG_OPT, NODE_LIST_OPT],
675 "[-n node...] <command>",
676 "Runs a command on all (or only some) nodes"),
677 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
678 "", "Show cluster configuration"),
679 'list-tags': (ListTags, ARGS_NONE,
680 [DEBUG_OPT], "", "List the tags of the cluster"),
681 'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
682 "tag...", "Add tags to the cluster"),
683 'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
684 "tag...", "Remove tags from the cluster"),
685 'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
686 [DEBUG_OPT], "", "Searches the tags on all objects on"
687 " the cluster for a given pattern (regex)"),
689 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
691 "drain|undrain|info", "Change queue properties"),
692 'watcher': (WatcherOps,
693 [ArgChoice(min=1, max=1,
694 choices=["pause", "continue", "info"]),
695 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
697 "{pause <timespec>|continue|info}", "Change watcher properties"),
698 'modify': (SetClusterParams, ARGS_NONE,
699 [DEBUG_OPT, BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
700 NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
702 "Alters the parameters of the cluster"),
705 if __name__ == '__main__':
706 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))