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)
348 def VerifyDisks(opts, args):
349 """Verify integrity of cluster disks.
351 @param opts: the command line options selected by the user
353 @param args: should be an empty list
355 @return: the desired exit code
358 op = opcodes.OpVerifyDisks()
359 result = SubmitOpCode(op)
360 if not isinstance(result, (list, tuple)) or len(result) != 3:
361 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
363 bad_nodes, instances, missing = result
365 retcode = constants.EXIT_SUCCESS
368 for node, text in bad_nodes.items():
369 ToStdout("Error gathering data on node %s: %s",
370 node, utils.SafeEncode(text[-400:]))
372 ToStdout("You need to fix these nodes first before fixing instances")
375 for iname in instances:
378 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
380 ToStdout("Activating disks for instance '%s'", iname)
382 except errors.GenericError, err:
383 nret, msg = FormatError(err)
385 ToStderr("Error activating disks for instance %s: %s", iname, msg)
388 for iname, ival in missing.iteritems():
389 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
391 ToStdout("Instance %s cannot be verified as it lives on"
392 " broken nodes", iname)
394 ToStdout("Instance %s has missing logical volumes:", iname)
396 for node, vol in ival:
397 if node in bad_nodes:
398 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
400 ToStdout("\t%s /dev/xenvg/%s", node, vol)
401 ToStdout("You need to run replace_disks for all the above"
402 " instances, if this message persist after fixing nodes.")
408 def RepairDiskSizes(opts, args):
409 """Verify sizes of cluster disks.
411 @param opts: the command line options selected by the user
413 @param args: optional list of instances to restrict check to
415 @return: the desired exit code
418 op = opcodes.OpRepairDiskSizes(instances=args)
423 def MasterFailover(opts, args):
424 """Failover the master node.
426 This command, when run on a non-master node, will cause the current
427 master to cease being master, and the non-master to become new
430 @param opts: the command line options selected by the user
432 @param args: should be an empty list
434 @return: the desired exit code
438 usertext = ("This will perform the failover even if most other nodes"
439 " are down, or if this node is outdated. This is dangerous"
440 " as it can lead to a non-consistent cluster. Check the"
441 " gnt-cluster(8) man page before proceeding. Continue?")
442 if not AskUser(usertext):
445 return bootstrap.MasterFailover(no_voting=opts.no_voting)
448 def SearchTags(opts, args):
449 """Searches the tags on all the cluster.
451 @param opts: the command line options selected by the user
453 @param args: should contain only one element, the tag pattern
455 @return: the desired exit code
458 op = opcodes.OpSearchTags(pattern=args[0])
459 result = SubmitOpCode(op)
462 result = list(result)
464 for path, tag in result:
465 ToStdout("%s %s", path, tag)
468 def SetClusterParams(opts, args):
469 """Modify the cluster.
471 @param opts: the command line options selected by the user
473 @param args: should be an empty list
475 @return: the desired exit code
478 if not (not opts.lvm_storage or opts.vg_name or
479 opts.enabled_hypervisors or opts.hvparams or
480 opts.beparams or opts.nicparams or
481 opts.candidate_pool_size is not None):
482 ToStderr("Please give at least one of the parameters.")
485 vg_name = opts.vg_name
486 if not opts.lvm_storage and opts.vg_name:
487 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
489 elif not opts.lvm_storage:
492 hvlist = opts.enabled_hypervisors
493 if hvlist is not None:
494 hvlist = hvlist.split(",")
496 # a list of (name, dict) we can pass directly to dict() (or [])
497 hvparams = dict(opts.hvparams)
498 for hv, hv_params in hvparams.iteritems():
499 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
501 beparams = opts.beparams
502 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
504 nicparams = opts.nicparams
505 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
507 op = opcodes.OpSetClusterParams(vg_name=vg_name,
508 enabled_hypervisors=hvlist,
512 candidate_pool_size=opts.candidate_pool_size)
517 def QueueOps(opts, args):
520 @param opts: the command line options selected by the user
522 @param args: should contain only one element, the subcommand
524 @return: the desired exit code
529 if command in ("drain", "undrain"):
530 drain_flag = command == "drain"
531 client.SetQueueDrainFlag(drain_flag)
532 elif command == "info":
533 result = client.QueryConfigValues(["drain_flag"])
538 ToStdout("The drain flag is %s" % val)
540 raise errors.OpPrereqError("Command '%s' is not valid." % command)
545 def _ShowWatcherPause(until):
546 if until is None or until < time.time():
547 ToStdout("The watcher is not paused.")
549 ToStdout("The watcher is paused until %s.", time.ctime(until))
552 def WatcherOps(opts, args):
553 """Watcher operations.
555 @param opts: the command line options selected by the user
557 @param args: should contain only one element, the subcommand
559 @return: the desired exit code
565 if command == "continue":
566 client.SetWatcherPause(None)
567 ToStdout("The watcher is no longer paused.")
569 elif command == "pause":
571 raise errors.OpPrereqError("Missing pause duration")
573 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
574 _ShowWatcherPause(result)
576 elif command == "info":
577 result = client.QueryConfigValues(["watcher_pause"])
578 _ShowWatcherPause(result)
581 raise errors.OpPrereqError("Command '%s' is not valid." % command)
586 # this is an option common to more than one command, so we declare
587 # it here and reuse it
588 node_option = cli_option("-n", "--node", action="append", dest="nodes",
589 help="Node to copy to (if not given, all nodes),"
590 " can be given multiple times",
591 metavar="<node>", default=[])
594 'init': (InitCluster, [ArgHost(min=1, max=1)],
596 cli_option("-s", "--secondary-ip", dest="secondary_ip",
597 help="Specify the secondary ip for this node;"
598 " if given, the entire cluster must have secondary"
600 metavar="ADDRESS", default=None),
601 cli_option("-m", "--mac-prefix", dest="mac_prefix",
602 help="Specify the mac prefix for the instance IP"
603 " addresses, in the format XX:XX:XX",
605 default=constants.DEFAULT_MAC_PREFIX,),
606 cli_option("-g", "--vg-name", dest="vg_name",
607 help="Specify the volume group name "
608 " (cluster-wide) for disk allocation [xenvg]",
611 cli_option("--master-netdev", dest="master_netdev",
612 help="Specify the node interface (cluster-wide)"
613 " on which the master IP address will be added "
614 " [%s]" % constants.DEFAULT_BRIDGE,
616 default=constants.DEFAULT_BRIDGE,),
617 cli_option("--file-storage-dir", dest="file_storage_dir",
618 help="Specify the default directory (cluster-wide)"
619 " for storing the file-based disks [%s]" %
620 constants.DEFAULT_FILE_STORAGE_DIR,
622 default=constants.DEFAULT_FILE_STORAGE_DIR,),
623 cli_option("--no-lvm-storage", dest="lvm_storage",
624 help="No support for lvm based instances"
626 action="store_false", default=True,),
627 cli_option("--no-etc-hosts", dest="modify_etc_hosts",
628 help="Don't modify /etc/hosts"
630 action="store_false", default=True,),
631 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
632 help="Comma-separated list of hypervisors",
634 default=constants.DEFAULT_ENABLED_HYPERVISOR),
635 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
636 help="Hypervisor and hypervisor options, in the format"
637 " hypervisor:option=value,option=value,...",
641 cli_option("-B", "--backend-parameters", dest="beparams",
642 type="keyval", default={},
643 help="Backend parameters"),
644 cli_option("-N", "--nic-parameters", dest="nicparams",
645 type="keyval", default={},
646 help="NIC parameters"),
647 cli_option("-C", "--candidate-pool-size",
648 default=constants.MASTER_POOL_SIZE_DEFAULT,
649 help="Set the candidate pool size",
650 dest="candidate_pool_size", type="int"),
652 "[opts...] <cluster_name>",
653 "Initialises a new cluster configuration"),
654 'destroy': (DestroyCluster, ARGS_NONE,
656 cli_option("--yes-do-it", dest="yes_do_it",
657 help="Destroy cluster",
658 action="store_true"),
660 "", "Destroy cluster"),
661 'rename': (RenameCluster, [ArgHost(min=1, max=1)],
662 [DEBUG_OPT, FORCE_OPT],
664 "Renames the cluster"),
665 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
667 "Forces a push of the configuration file and ssconf files"
668 " to the nodes in the cluster"),
669 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
670 cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
671 help="Skip N+1 memory redundancy tests",
675 "", "Does a check on the cluster configuration"),
676 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
677 "", "Does a check on the cluster disk status"),
678 'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
679 "", "Updates mismatches in recorded disk sizes"),
680 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
681 cli_option("--no-voting", dest="no_voting",
682 help="Skip node agreement check (dangerous)",
686 "", "Makes the current node the master"),
687 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
688 "", "Shows the cluster version"),
689 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
690 "", "Shows the cluster master"),
691 'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
692 [DEBUG_OPT, node_option],
693 "[-n node...] <filename>",
694 "Copies a file to all (or only some) nodes"),
695 'command': (RunClusterCommand, [ArgCommand(min=1)], [DEBUG_OPT, node_option],
696 "[-n node...] <command>",
697 "Runs a command on all (or only some) nodes"),
698 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
699 "", "Show cluster configuration"),
700 'list-tags': (ListTags, ARGS_NONE,
701 [DEBUG_OPT], "", "List the tags of the cluster"),
702 'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
703 "tag...", "Add tags to the cluster"),
704 'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
705 "tag...", "Remove tags from the cluster"),
706 'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
707 [DEBUG_OPT], "", "Searches the tags on all objects on"
708 " the cluster for a given pattern (regex)"),
710 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
712 "drain|undrain|info", "Change queue properties"),
713 'watcher': (WatcherOps,
714 [ArgChoice(min=1, max=1,
715 choices=["pause", "continue", "info"]),
716 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
718 "{pause <timespec>|continue|info}", "Change watcher properties"),
719 'modify': (SetClusterParams, ARGS_NONE,
721 cli_option("-g", "--vg-name", dest="vg_name",
722 help="Specify the volume group name "
723 " (cluster-wide) for disk allocation "
724 "and enable lvm based storage",
726 cli_option("--no-lvm-storage", dest="lvm_storage",
727 help="Disable support for lvm based instances"
729 action="store_false", default=True,),
730 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
731 help="Comma-separated list of hypervisors",
732 type="string", default=None),
733 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
734 help="Hypervisor and hypervisor options, in the"
736 " hypervisor:option=value,option=value,...",
740 cli_option("-B", "--backend-parameters", dest="beparams",
741 type="keyval", default={},
742 help="Backend parameters"),
743 cli_option("-N", "--nic-parameters", dest="nicparams",
744 type="keyval", default={},
745 help="NIC parameters"),
746 cli_option("-C", "--candidate-pool-size", default=None,
747 help="Set the candidate pool size",
748 dest="candidate_pool_size", type="int"),
751 "Alters the parameters of the cluster"),
754 if __name__ == '__main__':
755 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))