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
61 hvlist = hvlist.split(",")
63 hvparams = dict(opts.hvparams)
64 beparams = opts.beparams
65 nicparams = opts.nicparams
67 # prepare beparams dict
68 beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
69 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
71 # prepare nicparams dict
72 nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
73 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
75 # prepare hvparams dict
76 for hv in constants.HYPER_TYPES:
77 if hv not in hvparams:
79 hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
80 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
82 bootstrap.InitCluster(cluster_name=args[0],
83 secondary_ip=opts.secondary_ip,
85 mac_prefix=opts.mac_prefix,
86 master_netdev=opts.master_netdev,
87 file_storage_dir=opts.file_storage_dir,
88 enabled_hypervisors=hvlist,
92 candidate_pool_size=opts.candidate_pool_size,
93 modify_etc_hosts=opts.modify_etc_hosts,
95 op = opcodes.OpPostInitCluster()
101 def DestroyCluster(opts, args):
102 """Destroy the cluster.
104 @param opts: the command line options selected by the user
106 @param args: should be an empty list
108 @return: the desired exit code
111 if not opts.yes_do_it:
112 ToStderr("Destroying a cluster is irreversible. If you really want"
113 " destroy this cluster, supply the --yes-do-it option.")
116 op = opcodes.OpDestroyCluster()
117 master = SubmitOpCode(op)
118 # if we reached this, the opcode didn't fail; we can proceed to
119 # shutdown all the daemons
120 bootstrap.FinalizeClusterDestroy(master)
124 def RenameCluster(opts, args):
125 """Rename the cluster.
127 @param opts: the command line options selected by the user
129 @param args: should contain only one element, the new cluster name
131 @return: the desired exit code
136 usertext = ("This will rename the cluster to '%s'. If you are connected"
137 " over the network to the cluster name, the operation is very"
138 " dangerous as the IP address will be removed from the node"
139 " and the change may not go through. Continue?") % name
140 if not AskUser(usertext):
143 op = opcodes.OpRenameCluster(name=name)
148 def RedistributeConfig(opts, args):
149 """Forces push of the cluster configuration.
151 @param opts: the command line options selected by the user
153 @param args: empty list
155 @return: the desired exit code
158 op = opcodes.OpRedistributeConfig()
159 SubmitOrSend(op, opts)
163 def ShowClusterVersion(opts, args):
164 """Write version of ganeti software to the standard output.
166 @param opts: the command line options selected by the user
168 @param args: should be an empty list
170 @return: the desired exit code
174 result = cl.QueryClusterInfo()
175 ToStdout("Software version: %s", result["software_version"])
176 ToStdout("Internode protocol: %s", result["protocol_version"])
177 ToStdout("Configuration format: %s", result["config_version"])
178 ToStdout("OS api version: %s", result["os_api_version"])
179 ToStdout("Export interface: %s", result["export_version"])
183 def ShowClusterMaster(opts, args):
184 """Write name of master node to the standard output.
186 @param opts: the command line options selected by the user
188 @param args: should be an empty list
190 @return: the desired exit code
193 master = bootstrap.GetMaster()
197 def _PrintGroupedParams(paramsdict):
198 """Print Grouped parameters (be, nic, disk) by group.
200 @type paramsdict: dict of dicts
201 @param paramsdict: {group: {param: value, ...}, ...}
204 for gr_name, gr_dict in paramsdict.items():
205 ToStdout(" - %s:", gr_name)
206 for item, val in gr_dict.iteritems():
207 ToStdout(" %s: %s", item, val)
209 def ShowClusterConfig(opts, args):
210 """Shows cluster information.
212 @param opts: the command line options selected by the user
214 @param args: should be an empty list
216 @return: the desired exit code
220 result = cl.QueryClusterInfo()
222 ToStdout("Cluster name: %s", result["name"])
224 ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
225 ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
227 ToStdout("Master node: %s", result["master"])
229 ToStdout("Architecture (this node): %s (%s)",
230 result["architecture"][0], result["architecture"][1])
233 tags = ", ".join(utils.NiceSort(result["tags"]))
237 ToStdout("Tags: %s", tags)
239 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
240 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
242 ToStdout("Hypervisor parameters:")
243 _PrintGroupedParams(result["hvparams"])
245 ToStdout("Cluster parameters:")
246 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
247 ToStdout(" - master netdev: %s", result["master_netdev"])
248 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
249 ToStdout(" - file storage path: %s", result["file_storage_dir"])
251 ToStdout("Default instance parameters:")
252 _PrintGroupedParams(result["beparams"])
254 ToStdout("Default nic parameters:")
255 _PrintGroupedParams(result["nicparams"])
260 def ClusterCopyFile(opts, args):
261 """Copy a file from master to some nodes.
263 @param opts: the command line options selected by the user
265 @param args: should contain only one element, the path of
266 the file to be copied
268 @return: the desired exit code
272 if not os.path.exists(filename):
273 raise errors.OpPrereqError("No such filename '%s'" % filename)
277 myname = utils.HostInfo().name
279 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
281 results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
282 results = [name for name in results if name != myname]
284 srun = ssh.SshRunner(cluster_name=cluster_name)
286 if not srun.CopyFileToNode(node, filename):
287 ToStderr("Copy of file %s to node %s failed", filename, node)
292 def RunClusterCommand(opts, args):
293 """Run a command on some nodes.
295 @param opts: the command line options selected by the user
297 @param args: should contain the command to be run and its arguments
299 @return: the desired exit code
304 command = " ".join(args)
306 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
308 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
311 srun = ssh.SshRunner(cluster_name=cluster_name)
313 # Make sure master node is at list end
314 if master_node in nodes:
315 nodes.remove(master_node)
316 nodes.append(master_node)
319 result = srun.Run(name, "root", command)
320 ToStdout("------------------------------------------------")
321 ToStdout("node: %s", name)
322 ToStdout("%s", result.output)
323 ToStdout("return code = %s", result.exit_code)
328 def VerifyCluster(opts, args):
329 """Verify integrity of cluster, performing various test on nodes.
331 @param opts: the command line options selected by the user
333 @param args: should be an empty list
335 @return: the desired exit code
339 if opts.skip_nplusone_mem:
340 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
341 op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
342 verbose=opts.verbose,
343 error_codes=opts.error_codes,
344 debug_simulate_errors=opts.simulate_errors)
351 def VerifyDisks(opts, args):
352 """Verify integrity of cluster disks.
354 @param opts: the command line options selected by the user
356 @param args: should be an empty list
358 @return: the desired exit code
361 op = opcodes.OpVerifyDisks()
362 result = SubmitOpCode(op)
363 if not isinstance(result, (list, tuple)) or len(result) != 3:
364 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
366 bad_nodes, instances, missing = result
368 retcode = constants.EXIT_SUCCESS
371 for node, text in bad_nodes.items():
372 ToStdout("Error gathering data on node %s: %s",
373 node, utils.SafeEncode(text[-400:]))
375 ToStdout("You need to fix these nodes first before fixing instances")
378 for iname in instances:
381 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
383 ToStdout("Activating disks for instance '%s'", iname)
385 except errors.GenericError, err:
386 nret, msg = FormatError(err)
388 ToStderr("Error activating disks for instance %s: %s", iname, msg)
391 for iname, ival in missing.iteritems():
392 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
394 ToStdout("Instance %s cannot be verified as it lives on"
395 " broken nodes", iname)
397 ToStdout("Instance %s has missing logical volumes:", iname)
399 for node, vol in ival:
400 if node in bad_nodes:
401 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
403 ToStdout("\t%s /dev/xenvg/%s", node, vol)
404 ToStdout("You need to run replace_disks for all the above"
405 " instances, if this message persist after fixing nodes.")
411 def RepairDiskSizes(opts, args):
412 """Verify sizes of cluster disks.
414 @param opts: the command line options selected by the user
416 @param args: optional list of instances to restrict check to
418 @return: the desired exit code
421 op = opcodes.OpRepairDiskSizes(instances=args)
426 def MasterFailover(opts, args):
427 """Failover the master node.
429 This command, when run on a non-master node, will cause the current
430 master to cease being master, and the non-master to become new
433 @param opts: the command line options selected by the user
435 @param args: should be an empty list
437 @return: the desired exit code
441 usertext = ("This will perform the failover even if most other nodes"
442 " are down, or if this node is outdated. This is dangerous"
443 " as it can lead to a non-consistent cluster. Check the"
444 " gnt-cluster(8) man page before proceeding. Continue?")
445 if not AskUser(usertext):
448 return bootstrap.MasterFailover(no_voting=opts.no_voting)
451 def SearchTags(opts, args):
452 """Searches the tags on all the cluster.
454 @param opts: the command line options selected by the user
456 @param args: should contain only one element, the tag pattern
458 @return: the desired exit code
461 op = opcodes.OpSearchTags(pattern=args[0])
462 result = SubmitOpCode(op)
465 result = list(result)
467 for path, tag in result:
468 ToStdout("%s %s", path, tag)
471 def SetClusterParams(opts, args):
472 """Modify the cluster.
474 @param opts: the command line options selected by the user
476 @param args: should be an empty list
478 @return: the desired exit code
481 if not (not opts.lvm_storage or opts.vg_name or
482 opts.enabled_hypervisors or opts.hvparams or
483 opts.beparams or opts.nicparams or
484 opts.candidate_pool_size is not None):
485 ToStderr("Please give at least one of the parameters.")
488 vg_name = opts.vg_name
489 if not opts.lvm_storage and opts.vg_name:
490 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
492 elif not opts.lvm_storage:
495 hvlist = opts.enabled_hypervisors
496 if hvlist is not None:
497 hvlist = hvlist.split(",")
499 # a list of (name, dict) we can pass directly to dict() (or [])
500 hvparams = dict(opts.hvparams)
501 for hv, hv_params in hvparams.iteritems():
502 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
504 beparams = opts.beparams
505 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
507 nicparams = opts.nicparams
508 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
510 op = opcodes.OpSetClusterParams(vg_name=vg_name,
511 enabled_hypervisors=hvlist,
515 candidate_pool_size=opts.candidate_pool_size)
520 def QueueOps(opts, args):
523 @param opts: the command line options selected by the user
525 @param args: should contain only one element, the subcommand
527 @return: the desired exit code
532 if command in ("drain", "undrain"):
533 drain_flag = command == "drain"
534 client.SetQueueDrainFlag(drain_flag)
535 elif command == "info":
536 result = client.QueryConfigValues(["drain_flag"])
541 ToStdout("The drain flag is %s" % val)
543 raise errors.OpPrereqError("Command '%s' is not valid." % command)
548 def _ShowWatcherPause(until):
549 if until is None or until < time.time():
550 ToStdout("The watcher is not paused.")
552 ToStdout("The watcher is paused until %s.", time.ctime(until))
555 def WatcherOps(opts, args):
556 """Watcher operations.
558 @param opts: the command line options selected by the user
560 @param args: should contain only one element, the subcommand
562 @return: the desired exit code
568 if command == "continue":
569 client.SetWatcherPause(None)
570 ToStdout("The watcher is no longer paused.")
572 elif command == "pause":
574 raise errors.OpPrereqError("Missing pause duration")
576 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
577 _ShowWatcherPause(result)
579 elif command == "info":
580 result = client.QueryConfigValues(["watcher_pause"])
581 _ShowWatcherPause(result)
584 raise errors.OpPrereqError("Command '%s' is not valid." % command)
589 # this is an option common to more than one command, so we declare
590 # it here and reuse it
591 node_option = cli_option("-n", "--node", action="append", dest="nodes",
592 help="Node to copy to (if not given, all nodes),"
593 " can be given multiple times",
594 metavar="<node>", default=[])
597 'init': (InitCluster, [ArgHost(min=1, max=1)],
599 cli_option("-s", "--secondary-ip", dest="secondary_ip",
600 help="Specify the secondary ip for this node;"
601 " if given, the entire cluster must have secondary"
603 metavar="ADDRESS", default=None),
604 cli_option("-m", "--mac-prefix", dest="mac_prefix",
605 help="Specify the mac prefix for the instance IP"
606 " addresses, in the format XX:XX:XX",
608 default=constants.DEFAULT_MAC_PREFIX,),
609 cli_option("-g", "--vg-name", dest="vg_name",
610 help="Specify the volume group name "
611 " (cluster-wide) for disk allocation [xenvg]",
614 cli_option("--master-netdev", dest="master_netdev",
615 help="Specify the node interface (cluster-wide)"
616 " on which the master IP address will be added "
617 " [%s]" % constants.DEFAULT_BRIDGE,
619 default=constants.DEFAULT_BRIDGE,),
620 cli_option("--file-storage-dir", dest="file_storage_dir",
621 help="Specify the default directory (cluster-wide)"
622 " for storing the file-based disks [%s]" %
623 constants.DEFAULT_FILE_STORAGE_DIR,
625 default=constants.DEFAULT_FILE_STORAGE_DIR,),
626 cli_option("--no-lvm-storage", dest="lvm_storage",
627 help="No support for lvm based instances"
629 action="store_false", default=True,),
630 cli_option("--no-etc-hosts", dest="modify_etc_hosts",
631 help="Don't modify /etc/hosts"
633 action="store_false", default=True,),
634 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
635 help="Comma-separated list of hypervisors",
637 default=constants.DEFAULT_ENABLED_HYPERVISOR),
638 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
639 help="Hypervisor and hypervisor options, in the format"
640 " hypervisor:option=value,option=value,...",
645 cli_option("-N", "--nic-parameters", dest="nicparams",
646 type="keyval", default={},
647 help="NIC parameters"),
648 cli_option("-C", "--candidate-pool-size",
649 default=constants.MASTER_POOL_SIZE_DEFAULT,
650 help="Set the candidate pool size",
651 dest="candidate_pool_size", type="int"),
653 "[opts...] <cluster_name>",
654 "Initialises a new cluster configuration"),
655 'destroy': (DestroyCluster, ARGS_NONE,
657 cli_option("--yes-do-it", dest="yes_do_it",
658 help="Destroy cluster",
659 action="store_true"),
661 "", "Destroy cluster"),
662 'rename': (RenameCluster, [ArgHost(min=1, max=1)],
663 [DEBUG_OPT, FORCE_OPT],
665 "Renames the cluster"),
666 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
668 "Forces a push of the configuration file and ssconf files"
669 " to the nodes in the cluster"),
670 'verify': (VerifyCluster, ARGS_NONE,
671 [DEBUG_OPT, VERBOSE_OPT, DEBUG_SIMERR_OPT,
672 cli_option("--error-codes", dest="error_codes",
673 help="Enable parseable error messages",
674 action="store_true", default=False),
675 cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
676 help="Skip N+1 memory redundancy tests",
677 action="store_true", default=False),
679 "", "Does a check on the cluster configuration"),
680 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
681 "", "Does a check on the cluster disk status"),
682 'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
683 "", "Updates mismatches in recorded disk sizes"),
684 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
685 cli_option("--no-voting", dest="no_voting",
686 help="Skip node agreement check (dangerous)",
690 "", "Makes the current node the master"),
691 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
692 "", "Shows the cluster version"),
693 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
694 "", "Shows the cluster master"),
695 'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
696 [DEBUG_OPT, node_option],
697 "[-n node...] <filename>",
698 "Copies a file to all (or only some) nodes"),
699 'command': (RunClusterCommand, [ArgCommand(min=1)], [DEBUG_OPT, node_option],
700 "[-n node...] <command>",
701 "Runs a command on all (or only some) nodes"),
702 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
703 "", "Show cluster configuration"),
704 'list-tags': (ListTags, ARGS_NONE,
705 [DEBUG_OPT], "", "List the tags of the cluster"),
706 'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
707 "tag...", "Add tags to the cluster"),
708 'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
709 "tag...", "Remove tags from the cluster"),
710 'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
711 [DEBUG_OPT], "", "Searches the tags on all objects on"
712 " the cluster for a given pattern (regex)"),
714 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
716 "drain|undrain|info", "Change queue properties"),
717 'watcher': (WatcherOps,
718 [ArgChoice(min=1, max=1,
719 choices=["pause", "continue", "info"]),
720 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
722 "{pause <timespec>|continue|info}", "Change watcher properties"),
723 'modify': (SetClusterParams, ARGS_NONE,
725 cli_option("-g", "--vg-name", dest="vg_name",
726 help="Specify the volume group name "
727 " (cluster-wide) for disk allocation "
728 "and enable lvm based storage",
730 cli_option("--no-lvm-storage", dest="lvm_storage",
731 help="Disable support for lvm based instances"
733 action="store_false", default=True,),
734 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
735 help="Comma-separated list of hypervisors",
736 type="string", default=None),
737 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
738 help="Hypervisor and hypervisor options, in the"
740 " hypervisor:option=value,option=value,...",
745 cli_option("-N", "--nic-parameters", dest="nicparams",
746 type="keyval", default={},
747 help="NIC parameters"),
748 cli_option("-C", "--candidate-pool-size", default=None,
749 help="Set the candidate pool size",
750 dest="candidate_pool_size", type="int"),
753 "Alters the parameters of the cluster"),
756 if __name__ == '__main__':
757 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))