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)
27 from optparse import make_option
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])
232 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
233 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
235 ToStdout("Hypervisor parameters:")
236 _PrintGroupedParams(result["hvparams"])
238 ToStdout("Cluster parameters:")
239 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
240 ToStdout(" - master netdev: %s", result["master_netdev"])
241 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
242 ToStdout(" - file storage path: %s", result["file_storage_dir"])
244 ToStdout("Default instance parameters:")
245 _PrintGroupedParams(result["beparams"])
247 ToStdout("Default nic parameters:")
248 _PrintGroupedParams(result["nicparams"])
253 def ClusterCopyFile(opts, args):
254 """Copy a file from master to some nodes.
256 @param opts: the command line options selected by the user
258 @param args: should contain only one element, the path of
259 the file to be copied
261 @return: the desired exit code
265 if not os.path.exists(filename):
266 raise errors.OpPrereqError("No such filename '%s'" % filename)
270 myname = utils.HostInfo().name
272 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
274 results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
275 results = [name for name in results if name != myname]
277 srun = ssh.SshRunner(cluster_name=cluster_name)
279 if not srun.CopyFileToNode(node, filename):
280 ToStderr("Copy of file %s to node %s failed", filename, node)
285 def RunClusterCommand(opts, args):
286 """Run a command on some nodes.
288 @param opts: the command line options selected by the user
290 @param args: should contain the command to be run and its arguments
292 @return: the desired exit code
297 command = " ".join(args)
299 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
301 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
304 srun = ssh.SshRunner(cluster_name=cluster_name)
306 # Make sure master node is at list end
307 if master_node in nodes:
308 nodes.remove(master_node)
309 nodes.append(master_node)
312 result = srun.Run(name, "root", command)
313 ToStdout("------------------------------------------------")
314 ToStdout("node: %s", name)
315 ToStdout("%s", result.output)
316 ToStdout("return code = %s", result.exit_code)
321 def VerifyCluster(opts, args):
322 """Verify integrity of cluster, performing various test on nodes.
324 @param opts: the command line options selected by the user
326 @param args: should be an empty list
328 @return: the desired exit code
332 if opts.skip_nplusone_mem:
333 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
334 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
341 def VerifyDisks(opts, args):
342 """Verify integrity of cluster disks.
344 @param opts: the command line options selected by the user
346 @param args: should be an empty list
348 @return: the desired exit code
351 op = opcodes.OpVerifyDisks()
352 result = SubmitOpCode(op)
353 if not isinstance(result, (list, tuple)) or len(result) != 3:
354 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
356 bad_nodes, instances, missing = result
358 retcode = constants.EXIT_SUCCESS
361 for node, text in bad_nodes.items():
362 ToStdout("Error gathering data on node %s: %s",
363 node, utils.SafeEncode(text[-400:]))
365 ToStdout("You need to fix these nodes first before fixing instances")
368 for iname in instances:
371 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
373 ToStdout("Activating disks for instance '%s'", iname)
375 except errors.GenericError, err:
376 nret, msg = FormatError(err)
378 ToStderr("Error activating disks for instance %s: %s", iname, msg)
381 for iname, ival in missing.iteritems():
382 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
384 ToStdout("Instance %s cannot be verified as it lives on"
385 " broken nodes", iname)
387 ToStdout("Instance %s has missing logical volumes:", iname)
389 for node, vol in ival:
390 if node in bad_nodes:
391 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
393 ToStdout("\t%s /dev/xenvg/%s", node, vol)
394 ToStdout("You need to run replace_disks for all the above"
395 " instances, if this message persist after fixing nodes.")
401 def RepairDiskSizes(opts, args):
402 """Verify sizes of cluster disks.
404 @param opts: the command line options selected by the user
406 @param args: optional list of instances to restrict check to
408 @return: the desired exit code
411 op = opcodes.OpRepairDiskSizes(instances=args)
416 def MasterFailover(opts, args):
417 """Failover the master node.
419 This command, when run on a non-master node, will cause the current
420 master to cease being master, and the non-master to become new
423 @param opts: the command line options selected by the user
425 @param args: should be an empty list
427 @return: the desired exit code
431 usertext = ("This will perform the failover even if most other nodes"
432 " are down, or if this node is outdated. This is dangerous"
433 " as it can lead to a non-consistent cluster. Check the"
434 " gnt-cluster(8) man page before proceeding. Continue?")
435 if not AskUser(usertext):
438 return bootstrap.MasterFailover(no_voting=opts.no_voting)
441 def SearchTags(opts, args):
442 """Searches the tags on all the cluster.
444 @param opts: the command line options selected by the user
446 @param args: should contain only one element, the tag pattern
448 @return: the desired exit code
451 op = opcodes.OpSearchTags(pattern=args[0])
452 result = SubmitOpCode(op)
455 result = list(result)
457 for path, tag in result:
458 ToStdout("%s %s", path, tag)
461 def SetClusterParams(opts, args):
462 """Modify the cluster.
464 @param opts: the command line options selected by the user
466 @param args: should be an empty list
468 @return: the desired exit code
471 if not (not opts.lvm_storage or opts.vg_name or
472 opts.enabled_hypervisors or opts.hvparams or
473 opts.beparams or opts.nicparams or
474 opts.candidate_pool_size is not None):
475 ToStderr("Please give at least one of the parameters.")
478 vg_name = opts.vg_name
479 if not opts.lvm_storage and opts.vg_name:
480 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
482 elif not opts.lvm_storage:
485 hvlist = opts.enabled_hypervisors
486 if hvlist is not None:
487 hvlist = hvlist.split(",")
489 # a list of (name, dict) we can pass directly to dict() (or [])
490 hvparams = dict(opts.hvparams)
491 for hv, hv_params in hvparams.iteritems():
492 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
494 beparams = opts.beparams
495 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
497 nicparams = opts.nicparams
498 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
500 op = opcodes.OpSetClusterParams(vg_name=vg_name,
501 enabled_hypervisors=hvlist,
505 candidate_pool_size=opts.candidate_pool_size)
510 def QueueOps(opts, args):
513 @param opts: the command line options selected by the user
515 @param args: should contain only one element, the subcommand
517 @return: the desired exit code
522 if command in ("drain", "undrain"):
523 drain_flag = command == "drain"
524 client.SetQueueDrainFlag(drain_flag)
525 elif command == "info":
526 result = client.QueryConfigValues(["drain_flag"])
531 ToStdout("The drain flag is %s" % val)
533 raise errors.OpPrereqError("Command '%s' is not valid." % command)
537 # this is an option common to more than one command, so we declare
538 # it here and reuse it
539 node_option = make_option("-n", "--node", action="append", dest="nodes",
540 help="Node to copy to (if not given, all nodes),"
541 " can be given multiple times",
542 metavar="<node>", default=[])
545 'init': (InitCluster, ARGS_ONE,
547 make_option("-s", "--secondary-ip", dest="secondary_ip",
548 help="Specify the secondary ip for this node;"
549 " if given, the entire cluster must have secondary"
551 metavar="ADDRESS", default=None),
552 make_option("-m", "--mac-prefix", dest="mac_prefix",
553 help="Specify the mac prefix for the instance IP"
554 " addresses, in the format XX:XX:XX",
556 default=constants.DEFAULT_MAC_PREFIX,),
557 make_option("-g", "--vg-name", dest="vg_name",
558 help="Specify the volume group name "
559 " (cluster-wide) for disk allocation [xenvg]",
562 make_option("--master-netdev", dest="master_netdev",
563 help="Specify the node interface (cluster-wide)"
564 " on which the master IP address will be added "
565 " [%s]" % constants.DEFAULT_BRIDGE,
567 default=constants.DEFAULT_BRIDGE,),
568 make_option("--file-storage-dir", dest="file_storage_dir",
569 help="Specify the default directory (cluster-wide)"
570 " for storing the file-based disks [%s]" %
571 constants.DEFAULT_FILE_STORAGE_DIR,
573 default=constants.DEFAULT_FILE_STORAGE_DIR,),
574 make_option("--no-lvm-storage", dest="lvm_storage",
575 help="No support for lvm based instances"
577 action="store_false", default=True,),
578 make_option("--no-etc-hosts", dest="modify_etc_hosts",
579 help="Don't modify /etc/hosts"
581 action="store_false", default=True,),
582 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
583 help="Comma-separated list of hypervisors",
585 default=constants.DEFAULT_ENABLED_HYPERVISOR),
586 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
587 help="Hypervisor and hypervisor options, in the"
589 " hypervisor:option=value,option=value,...",
593 cli_option("-B", "--backend-parameters", dest="beparams",
594 type="keyval", default={},
595 help="Backend parameters"),
596 cli_option("-N", "--nic-parameters", dest="nicparams",
597 type="keyval", default={},
598 help="NIC parameters"),
599 make_option("-C", "--candidate-pool-size",
600 default=constants.MASTER_POOL_SIZE_DEFAULT,
601 help="Set the candidate pool size",
602 dest="candidate_pool_size", type="int"),
604 "[opts...] <cluster_name>",
605 "Initialises a new cluster configuration"),
606 'destroy': (DestroyCluster, ARGS_NONE,
608 make_option("--yes-do-it", dest="yes_do_it",
609 help="Destroy cluster",
610 action="store_true"),
612 "", "Destroy cluster"),
613 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
615 "Renames the cluster"),
616 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
618 "Forces a push of the configuration file and ssconf files"
619 " to the nodes in the cluster"),
620 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
621 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
622 help="Skip N+1 memory redundancy tests",
626 "", "Does a check on the cluster configuration"),
627 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
628 "", "Does a check on the cluster disk status"),
629 'repair-disk-sizes': (RepairDiskSizes, ARGS_ANY, [DEBUG_OPT],
630 "", "Updates mismatches in recorded disk sizes"),
631 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
632 make_option("--no-voting", dest="no_voting",
633 help="Skip node agreement check (dangerous)",
637 "", "Makes the current node the master"),
638 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
639 "", "Shows the cluster version"),
640 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
641 "", "Shows the cluster master"),
642 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
643 "[-n node...] <filename>",
644 "Copies a file to all (or only some) nodes"),
645 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
646 "[-n node...] <command>",
647 "Runs a command on all (or only some) nodes"),
648 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
649 "", "Show cluster configuration"),
650 'list-tags': (ListTags, ARGS_NONE,
651 [DEBUG_OPT], "", "List the tags of the cluster"),
652 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
653 "tag...", "Add tags to the cluster"),
654 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
655 "tag...", "Remove tags from the cluster"),
656 'search-tags': (SearchTags, ARGS_ONE,
657 [DEBUG_OPT], "", "Searches the tags on all objects on"
658 " the cluster for a given pattern (regex)"),
659 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
660 "drain|undrain|info", "Change queue properties"),
661 'modify': (SetClusterParams, ARGS_NONE,
663 make_option("-g", "--vg-name", dest="vg_name",
664 help="Specify the volume group name "
665 " (cluster-wide) for disk allocation "
666 "and enable lvm based storage",
668 make_option("--no-lvm-storage", dest="lvm_storage",
669 help="Disable support for lvm based instances"
671 action="store_false", default=True,),
672 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
673 help="Comma-separated list of hypervisors",
674 type="string", default=None),
675 cli_option("-H", "--hypervisor-parameters", dest="hvparams",
676 help="Hypervisor and hypervisor options, in the"
678 " hypervisor:option=value,option=value,...",
682 cli_option("-B", "--backend-parameters", dest="beparams",
683 type="keyval", default={},
684 help="Backend parameters"),
685 cli_option("-N", "--nic-parameters", dest="nicparams",
686 type="keyval", default={},
687 help="NIC parameters"),
688 make_option("-C", "--candidate-pool-size", default=None,
689 help="Set the candidate pool size",
690 dest="candidate_pool_size", type="int"),
693 "Alters the parameters of the cluster"),
696 if __name__ == '__main__':
697 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))