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)
350 def VerifyDisks(opts, args):
351 """Verify integrity of cluster disks.
353 @param opts: the command line options selected by the user
355 @param args: should be an empty list
357 @return: the desired exit code
360 op = opcodes.OpVerifyDisks()
361 result = SubmitOpCode(op)
362 if not isinstance(result, (list, tuple)) or len(result) != 3:
363 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
365 bad_nodes, instances, missing = result
367 retcode = constants.EXIT_SUCCESS
370 for node, text in bad_nodes.items():
371 ToStdout("Error gathering data on node %s: %s",
372 node, utils.SafeEncode(text[-400:]))
374 ToStdout("You need to fix these nodes first before fixing instances")
377 for iname in instances:
380 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
382 ToStdout("Activating disks for instance '%s'", iname)
384 except errors.GenericError, err:
385 nret, msg = FormatError(err)
387 ToStderr("Error activating disks for instance %s: %s", iname, msg)
390 for iname, ival in missing.iteritems():
391 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
393 ToStdout("Instance %s cannot be verified as it lives on"
394 " broken nodes", iname)
396 ToStdout("Instance %s has missing logical volumes:", iname)
398 for node, vol in ival:
399 if node in bad_nodes:
400 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
402 ToStdout("\t%s /dev/xenvg/%s", node, vol)
403 ToStdout("You need to run replace_disks for all the above"
404 " instances, if this message persist after fixing nodes.")
410 def RepairDiskSizes(opts, args):
411 """Verify sizes of cluster disks.
413 @param opts: the command line options selected by the user
415 @param args: optional list of instances to restrict check to
417 @return: the desired exit code
420 op = opcodes.OpRepairDiskSizes(instances=args)
425 def MasterFailover(opts, args):
426 """Failover the master node.
428 This command, when run on a non-master node, will cause the current
429 master to cease being master, and the non-master to become new
432 @param opts: the command line options selected by the user
434 @param args: should be an empty list
436 @return: the desired exit code
440 usertext = ("This will perform the failover even if most other nodes"
441 " are down, or if this node is outdated. This is dangerous"
442 " as it can lead to a non-consistent cluster. Check the"
443 " gnt-cluster(8) man page before proceeding. Continue?")
444 if not AskUser(usertext):
447 return bootstrap.MasterFailover(no_voting=opts.no_voting)
450 def SearchTags(opts, args):
451 """Searches the tags on all the cluster.
453 @param opts: the command line options selected by the user
455 @param args: should contain only one element, the tag pattern
457 @return: the desired exit code
460 op = opcodes.OpSearchTags(pattern=args[0])
461 result = SubmitOpCode(op)
464 result = list(result)
466 for path, tag in result:
467 ToStdout("%s %s", path, tag)
470 def SetClusterParams(opts, args):
471 """Modify the cluster.
473 @param opts: the command line options selected by the user
475 @param args: should be an empty list
477 @return: the desired exit code
480 if not (not opts.lvm_storage or opts.vg_name or
481 opts.enabled_hypervisors or opts.hvparams or
482 opts.beparams or opts.nicparams or
483 opts.candidate_pool_size is not None):
484 ToStderr("Please give at least one of the parameters.")
487 vg_name = opts.vg_name
488 if not opts.lvm_storage and opts.vg_name:
489 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
491 elif not opts.lvm_storage:
494 hvlist = opts.enabled_hypervisors
495 if hvlist is not None:
496 hvlist = hvlist.split(",")
498 # a list of (name, dict) we can pass directly to dict() (or [])
499 hvparams = dict(opts.hvparams)
500 for hv, hv_params in hvparams.iteritems():
501 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
503 beparams = opts.beparams
504 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
506 nicparams = opts.nicparams
507 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
509 op = opcodes.OpSetClusterParams(vg_name=vg_name,
510 enabled_hypervisors=hvlist,
514 candidate_pool_size=opts.candidate_pool_size)
519 def QueueOps(opts, args):
522 @param opts: the command line options selected by the user
524 @param args: should contain only one element, the subcommand
526 @return: the desired exit code
531 if command in ("drain", "undrain"):
532 drain_flag = command == "drain"
533 client.SetQueueDrainFlag(drain_flag)
534 elif command == "info":
535 result = client.QueryConfigValues(["drain_flag"])
540 ToStdout("The drain flag is %s" % val)
542 raise errors.OpPrereqError("Command '%s' is not valid." % command)
547 def _ShowWatcherPause(until):
548 if until is None or until < time.time():
549 ToStdout("The watcher is not paused.")
551 ToStdout("The watcher is paused until %s.", time.ctime(until))
554 def WatcherOps(opts, args):
555 """Watcher operations.
557 @param opts: the command line options selected by the user
559 @param args: should contain only one element, the subcommand
561 @return: the desired exit code
567 if command == "continue":
568 client.SetWatcherPause(None)
569 ToStdout("The watcher is no longer paused.")
571 elif command == "pause":
573 raise errors.OpPrereqError("Missing pause duration")
575 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
576 _ShowWatcherPause(result)
578 elif command == "info":
579 result = client.QueryConfigValues(["watcher_pause"])
580 _ShowWatcherPause(result)
583 raise errors.OpPrereqError("Command '%s' is not valid." % command)
588 # this is an option common to more than one command, so we declare
589 # it here and reuse it
590 node_option = cli_option("-n", "--node", action="append", dest="nodes",
591 help="Node to copy to (if not given, all nodes),"
592 " can be given multiple times",
593 metavar="<node>", default=[])
596 'init': (InitCluster, [ArgHost(min=1, max=1)],
598 cli_option("-s", "--secondary-ip", dest="secondary_ip",
599 help="Specify the secondary ip for this node;"
600 " if given, the entire cluster must have secondary"
602 metavar="ADDRESS", default=None),
603 cli_option("-m", "--mac-prefix", dest="mac_prefix",
604 help="Specify the mac prefix for the instance IP"
605 " addresses, in the format XX:XX:XX",
607 default=constants.DEFAULT_MAC_PREFIX,),
608 cli_option("-g", "--vg-name", dest="vg_name",
609 help="Specify the volume group name "
610 " (cluster-wide) for disk allocation [xenvg]",
613 cli_option("--master-netdev", dest="master_netdev",
614 help="Specify the node interface (cluster-wide)"
615 " on which the master IP address will be added "
616 " [%s]" % constants.DEFAULT_BRIDGE,
618 default=constants.DEFAULT_BRIDGE,),
619 cli_option("--file-storage-dir", dest="file_storage_dir",
620 help="Specify the default directory (cluster-wide)"
621 " for storing the file-based disks [%s]" %
622 constants.DEFAULT_FILE_STORAGE_DIR,
624 default=constants.DEFAULT_FILE_STORAGE_DIR,),
625 cli_option("--no-lvm-storage", dest="lvm_storage",
626 help="No support for lvm based instances"
628 action="store_false", default=True,),
629 cli_option("--no-etc-hosts", dest="modify_etc_hosts",
630 help="Don't modify /etc/hosts"
632 action="store_false", default=True,),
633 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
634 help="Comma-separated list of hypervisors",
636 default=constants.DEFAULT_ENABLED_HYPERVISOR),
637 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
638 help="Hypervisor and hypervisor options, in the format"
639 " hypervisor:option=value,option=value,...",
643 cli_option("-B", "--backend-parameters", dest="beparams",
644 type="keyval", default={},
645 help="Backend parameters"),
646 cli_option("-N", "--nic-parameters", dest="nicparams",
647 type="keyval", default={},
648 help="NIC parameters"),
649 cli_option("-C", "--candidate-pool-size",
650 default=constants.MASTER_POOL_SIZE_DEFAULT,
651 help="Set the candidate pool size",
652 dest="candidate_pool_size", type="int"),
654 "[opts...] <cluster_name>",
655 "Initialises a new cluster configuration"),
656 'destroy': (DestroyCluster, ARGS_NONE,
658 cli_option("--yes-do-it", dest="yes_do_it",
659 help="Destroy cluster",
660 action="store_true"),
662 "", "Destroy cluster"),
663 'rename': (RenameCluster, [ArgHost(min=1, max=1)],
664 [DEBUG_OPT, FORCE_OPT],
666 "Renames the cluster"),
667 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
669 "Forces a push of the configuration file and ssconf files"
670 " to the nodes in the cluster"),
671 'verify': (VerifyCluster, ARGS_NONE,
672 [DEBUG_OPT, VERBOSE_OPT,
673 cli_option("--error-codes", dest="error_codes",
674 help="Enable parseable error messages",
675 action="store_true", default=False),
676 cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
677 help="Skip N+1 memory redundancy tests",
678 action="store_true", default=False),
680 "", "Does a check on the cluster configuration"),
681 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
682 "", "Does a check on the cluster disk status"),
683 'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
684 "", "Updates mismatches in recorded disk sizes"),
685 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
686 cli_option("--no-voting", dest="no_voting",
687 help="Skip node agreement check (dangerous)",
691 "", "Makes the current node the master"),
692 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
693 "", "Shows the cluster version"),
694 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
695 "", "Shows the cluster master"),
696 'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
697 [DEBUG_OPT, node_option],
698 "[-n node...] <filename>",
699 "Copies a file to all (or only some) nodes"),
700 'command': (RunClusterCommand, [ArgCommand(min=1)], [DEBUG_OPT, node_option],
701 "[-n node...] <command>",
702 "Runs a command on all (or only some) nodes"),
703 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
704 "", "Show cluster configuration"),
705 'list-tags': (ListTags, ARGS_NONE,
706 [DEBUG_OPT], "", "List the tags of the cluster"),
707 'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
708 "tag...", "Add tags to the cluster"),
709 'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
710 "tag...", "Remove tags from the cluster"),
711 'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
712 [DEBUG_OPT], "", "Searches the tags on all objects on"
713 " the cluster for a given pattern (regex)"),
715 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
717 "drain|undrain|info", "Change queue properties"),
718 'watcher': (WatcherOps,
719 [ArgChoice(min=1, max=1,
720 choices=["pause", "continue", "info"]),
721 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
723 "{pause <timespec>|continue|info}", "Change watcher properties"),
724 'modify': (SetClusterParams, ARGS_NONE,
726 cli_option("-g", "--vg-name", dest="vg_name",
727 help="Specify the volume group name "
728 " (cluster-wide) for disk allocation "
729 "and enable lvm based storage",
731 cli_option("--no-lvm-storage", dest="lvm_storage",
732 help="Disable support for lvm based instances"
734 action="store_false", default=True,),
735 cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
736 help="Comma-separated list of hypervisors",
737 type="string", default=None),
738 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
739 help="Hypervisor and hypervisor options, in the"
741 " hypervisor:option=value,option=value,...",
745 cli_option("-B", "--backend-parameters", dest="beparams",
746 type="keyval", default={},
747 help="Backend parameters"),
748 cli_option("-N", "--nic-parameters", dest="nicparams",
749 type="keyval", default={},
750 help="NIC parameters"),
751 cli_option("-C", "--candidate-pool-size", default=None,
752 help="Set the candidate pool size",
753 dest="candidate_pool_size", type="int"),
756 "Alters the parameters of the cluster"),
759 if __name__ == '__main__':
760 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))