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 if opts.mac_prefix is None:
88 opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
90 bootstrap.InitCluster(cluster_name=args[0],
91 secondary_ip=opts.secondary_ip,
93 mac_prefix=opts.mac_prefix,
94 master_netdev=opts.master_netdev,
95 file_storage_dir=opts.file_storage_dir,
96 enabled_hypervisors=hvlist,
100 candidate_pool_size=opts.candidate_pool_size,
101 modify_etc_hosts=opts.modify_etc_hosts,
102 modify_ssh_setup=opts.modify_ssh_setup,
104 op = opcodes.OpPostInitCluster()
110 def DestroyCluster(opts, args):
111 """Destroy the cluster.
113 @param opts: the command line options selected by the user
115 @param args: should be an empty list
117 @return: the desired exit code
120 if not opts.yes_do_it:
121 ToStderr("Destroying a cluster is irreversible. If you really want"
122 " destroy this cluster, supply the --yes-do-it option.")
125 op = opcodes.OpDestroyCluster()
126 master = SubmitOpCode(op)
127 # if we reached this, the opcode didn't fail; we can proceed to
128 # shutdown all the daemons
129 bootstrap.FinalizeClusterDestroy(master)
133 def RenameCluster(opts, args):
134 """Rename the cluster.
136 @param opts: the command line options selected by the user
138 @param args: should contain only one element, the new cluster name
140 @return: the desired exit code
145 usertext = ("This will rename the cluster to '%s'. If you are connected"
146 " over the network to the cluster name, the operation is very"
147 " dangerous as the IP address will be removed from the node"
148 " and the change may not go through. Continue?") % name
149 if not AskUser(usertext):
152 op = opcodes.OpRenameCluster(name=name)
157 def RedistributeConfig(opts, args):
158 """Forces push of the cluster configuration.
160 @param opts: the command line options selected by the user
162 @param args: empty list
164 @return: the desired exit code
167 op = opcodes.OpRedistributeConfig()
168 SubmitOrSend(op, opts)
172 def ShowClusterVersion(opts, args):
173 """Write version of ganeti software to the standard output.
175 @param opts: the command line options selected by the user
177 @param args: should be an empty list
179 @return: the desired exit code
183 result = cl.QueryClusterInfo()
184 ToStdout("Software version: %s", result["software_version"])
185 ToStdout("Internode protocol: %s", result["protocol_version"])
186 ToStdout("Configuration format: %s", result["config_version"])
187 ToStdout("OS api version: %s", result["os_api_version"])
188 ToStdout("Export interface: %s", result["export_version"])
192 def ShowClusterMaster(opts, args):
193 """Write name of master node to the standard output.
195 @param opts: the command line options selected by the user
197 @param args: should be an empty list
199 @return: the desired exit code
202 master = bootstrap.GetMaster()
206 def _PrintGroupedParams(paramsdict):
207 """Print Grouped parameters (be, nic, disk) by group.
209 @type paramsdict: dict of dicts
210 @param paramsdict: {group: {param: value, ...}, ...}
213 for gr_name, gr_dict in paramsdict.items():
214 ToStdout(" - %s:", gr_name)
215 for item, val in gr_dict.iteritems():
216 ToStdout(" %s: %s", item, val)
218 def ShowClusterConfig(opts, args):
219 """Shows cluster information.
221 @param opts: the command line options selected by the user
223 @param args: should be an empty list
225 @return: the desired exit code
229 result = cl.QueryClusterInfo()
231 ToStdout("Cluster name: %s", result["name"])
232 ToStdout("Cluster UUID: %s", result["uuid"])
234 ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
235 ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
237 ToStdout("Master node: %s", result["master"])
239 ToStdout("Architecture (this node): %s (%s)",
240 result["architecture"][0], result["architecture"][1])
243 tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
247 ToStdout("Tags: %s", tags)
249 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
250 ToStdout("Enabled hypervisors: %s",
251 utils.CommaJoin(result["enabled_hypervisors"]))
253 ToStdout("Hypervisor parameters:")
254 _PrintGroupedParams(result["hvparams"])
256 ToStdout("Cluster parameters:")
257 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
258 ToStdout(" - master netdev: %s", result["master_netdev"])
259 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
260 ToStdout(" - file storage path: %s", result["file_storage_dir"])
262 ToStdout("Default instance parameters:")
263 _PrintGroupedParams(result["beparams"])
265 ToStdout("Default nic parameters:")
266 _PrintGroupedParams(result["nicparams"])
271 def ClusterCopyFile(opts, args):
272 """Copy a file from master to some nodes.
274 @param opts: the command line options selected by the user
276 @param args: should contain only one element, the path of
277 the file to be copied
279 @return: the desired exit code
283 if not os.path.exists(filename):
284 raise errors.OpPrereqError("No such filename '%s'" % filename,
289 myname = utils.GetHostInfo().name
291 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
293 results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
294 results = [name for name in results if name != myname]
296 srun = ssh.SshRunner(cluster_name=cluster_name)
298 if not srun.CopyFileToNode(node, filename):
299 ToStderr("Copy of file %s to node %s failed", filename, node)
304 def RunClusterCommand(opts, args):
305 """Run a command on some nodes.
307 @param opts: the command line options selected by the user
309 @param args: should contain the command to be run and its arguments
311 @return: the desired exit code
316 command = " ".join(args)
318 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
320 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
323 srun = ssh.SshRunner(cluster_name=cluster_name)
325 # Make sure master node is at list end
326 if master_node in nodes:
327 nodes.remove(master_node)
328 nodes.append(master_node)
331 result = srun.Run(name, "root", command)
332 ToStdout("------------------------------------------------")
333 ToStdout("node: %s", name)
334 ToStdout("%s", result.output)
335 ToStdout("return code = %s", result.exit_code)
340 def VerifyCluster(opts, args):
341 """Verify integrity of cluster, performing various test on nodes.
343 @param opts: the command line options selected by the user
345 @param args: should be an empty list
347 @return: the desired exit code
351 if opts.skip_nplusone_mem:
352 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
353 op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
354 verbose=opts.verbose,
355 error_codes=opts.error_codes,
356 debug_simulate_errors=opts.simulate_errors)
363 def VerifyDisks(opts, args):
364 """Verify integrity of cluster disks.
366 @param opts: the command line options selected by the user
368 @param args: should be an empty list
370 @return: the desired exit code
373 op = opcodes.OpVerifyDisks()
374 result = SubmitOpCode(op)
375 if not isinstance(result, (list, tuple)) or len(result) != 3:
376 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
378 bad_nodes, instances, missing = result
380 retcode = constants.EXIT_SUCCESS
383 for node, text in bad_nodes.items():
384 ToStdout("Error gathering data on node %s: %s",
385 node, utils.SafeEncode(text[-400:]))
387 ToStdout("You need to fix these nodes first before fixing instances")
390 for iname in instances:
393 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
395 ToStdout("Activating disks for instance '%s'", iname)
397 except errors.GenericError, err:
398 nret, msg = FormatError(err)
400 ToStderr("Error activating disks for instance %s: %s", iname, msg)
403 for iname, ival in missing.iteritems():
404 all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
406 ToStdout("Instance %s cannot be verified as it lives on"
407 " broken nodes", iname)
409 ToStdout("Instance %s has missing logical volumes:", iname)
411 for node, vol in ival:
412 if node in bad_nodes:
413 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
415 ToStdout("\t%s /dev/xenvg/%s", node, vol)
416 ToStdout("You need to run replace_disks for all the above"
417 " instances, if this message persist after fixing nodes.")
423 def RepairDiskSizes(opts, args):
424 """Verify sizes of cluster disks.
426 @param opts: the command line options selected by the user
428 @param args: optional list of instances to restrict check to
430 @return: the desired exit code
433 op = opcodes.OpRepairDiskSizes(instances=args)
438 def MasterFailover(opts, args):
439 """Failover the master node.
441 This command, when run on a non-master node, will cause the current
442 master to cease being master, and the non-master to become new
445 @param opts: the command line options selected by the user
447 @param args: should be an empty list
449 @return: the desired exit code
453 usertext = ("This will perform the failover even if most other nodes"
454 " are down, or if this node is outdated. This is dangerous"
455 " as it can lead to a non-consistent cluster. Check the"
456 " gnt-cluster(8) man page before proceeding. Continue?")
457 if not AskUser(usertext):
460 return bootstrap.MasterFailover(no_voting=opts.no_voting)
463 def SearchTags(opts, args):
464 """Searches the tags on all the cluster.
466 @param opts: the command line options selected by the user
468 @param args: should contain only one element, the tag pattern
470 @return: the desired exit code
473 op = opcodes.OpSearchTags(pattern=args[0])
474 result = SubmitOpCode(op)
477 result = list(result)
479 for path, tag in result:
480 ToStdout("%s %s", path, tag)
483 def SetClusterParams(opts, args):
484 """Modify the cluster.
486 @param opts: the command line options selected by the user
488 @param args: should be an empty list
490 @return: the desired exit code
493 if not (not opts.lvm_storage or opts.vg_name or
494 opts.enabled_hypervisors or opts.hvparams or
495 opts.beparams or opts.nicparams or
496 opts.candidate_pool_size is not None):
497 ToStderr("Please give at least one of the parameters.")
500 vg_name = opts.vg_name
501 if not opts.lvm_storage and opts.vg_name:
502 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
504 elif not opts.lvm_storage:
507 hvlist = opts.enabled_hypervisors
508 if hvlist is not None:
509 hvlist = hvlist.split(",")
511 # a list of (name, dict) we can pass directly to dict() (or [])
512 hvparams = dict(opts.hvparams)
513 for hv, hv_params in hvparams.iteritems():
514 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
516 beparams = opts.beparams
517 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
519 nicparams = opts.nicparams
520 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
522 op = opcodes.OpSetClusterParams(vg_name=vg_name,
523 enabled_hypervisors=hvlist,
527 candidate_pool_size=opts.candidate_pool_size)
532 def QueueOps(opts, args):
535 @param opts: the command line options selected by the user
537 @param args: should contain only one element, the subcommand
539 @return: the desired exit code
544 if command in ("drain", "undrain"):
545 drain_flag = command == "drain"
546 client.SetQueueDrainFlag(drain_flag)
547 elif command == "info":
548 result = client.QueryConfigValues(["drain_flag"])
553 ToStdout("The drain flag is %s" % val)
555 raise errors.OpPrereqError("Command '%s' is not valid." % command,
561 def _ShowWatcherPause(until):
562 if until is None or until < time.time():
563 ToStdout("The watcher is not paused.")
565 ToStdout("The watcher is paused until %s.", time.ctime(until))
568 def WatcherOps(opts, args):
569 """Watcher operations.
571 @param opts: the command line options selected by the user
573 @param args: should contain only one element, the subcommand
575 @return: the desired exit code
581 if command == "continue":
582 client.SetWatcherPause(None)
583 ToStdout("The watcher is no longer paused.")
585 elif command == "pause":
587 raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
589 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
590 _ShowWatcherPause(result)
592 elif command == "info":
593 result = client.QueryConfigValues(["watcher_pause"])
594 _ShowWatcherPause(result)
597 raise errors.OpPrereqError("Command '%s' is not valid." % command,
605 InitCluster, [ArgHost(min=1, max=1)],
606 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
607 HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
608 NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
609 SECONDARY_IP_OPT, VG_NAME_OPT],
610 "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
612 DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
613 "", "Destroy cluster"),
615 RenameCluster, [ArgHost(min=1, max=1)],
618 "Renames the cluster"),
620 RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
621 "", "Forces a push of the configuration file and ssconf files"
622 " to the nodes in the cluster"),
624 VerifyCluster, ARGS_NONE,
625 [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
626 "", "Does a check on the cluster configuration"),
628 VerifyDisks, ARGS_NONE, [],
629 "", "Does a check on the cluster disk status"),
630 'repair-disk-sizes': (
631 RepairDiskSizes, ARGS_MANY_INSTANCES, [],
632 "", "Updates mismatches in recorded disk sizes"),
634 MasterFailover, ARGS_NONE, [NOVOTING_OPT],
635 "", "Makes the current node the master"),
637 ShowClusterVersion, ARGS_NONE, [],
638 "", "Shows the cluster version"),
640 ShowClusterMaster, ARGS_NONE, [],
641 "", "Shows the cluster master"),
643 ClusterCopyFile, [ArgFile(min=1, max=1)],
645 "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
647 RunClusterCommand, [ArgCommand(min=1)],
649 "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
651 ShowClusterConfig, ARGS_NONE, [],
652 "", "Show cluster configuration"),
654 ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
656 AddTags, [ArgUnknown()], [TAG_SRC_OPT],
657 "tag...", "Add tags to the cluster"),
659 RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
660 "tag...", "Remove tags from the cluster"),
662 SearchTags, [ArgUnknown(min=1, max=1)],
663 [], "", "Searches the tags on all objects on"
664 " the cluster for a given pattern (regex)"),
667 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
668 [], "drain|undrain|info", "Change queue properties"),
671 [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
672 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
674 "{pause <timespec>|continue|info}", "Change watcher properties"),
676 SetClusterParams, ARGS_NONE,
677 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
678 NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT],
680 "Alters the parameters of the cluster"),
683 if __name__ == '__main__':
684 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))