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
40 def InitCluster(opts, args):
41 """Initialize the cluster.
43 @param opts: the command line options selected by the user
45 @param args: should contain only one element, the desired
48 @return: the desired exit code
51 if not opts.lvm_storage and opts.vg_name:
52 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
55 vg_name = opts.vg_name
56 if opts.lvm_storage and not opts.vg_name:
57 vg_name = constants.DEFAULT_VG
59 hvlist = opts.enabled_hypervisors
60 if hvlist is not None:
61 hvlist = hvlist.split(",")
63 hvlist = [opts.default_hypervisor]
65 # avoid an impossible situation
66 if opts.default_hypervisor not in hvlist:
67 ToStderr("The default hypervisor requested (%s) is not"
68 " within the enabled hypervisor list (%s)" %
69 (opts.default_hypervisor, hvlist))
72 hvparams = dict(opts.hvparams)
74 beparams = opts.beparams
75 # check for invalid parameters
76 for parameter in beparams:
77 if parameter not in constants.BES_PARAMETERS:
78 ToStderr("Invalid backend parameter: %s", parameter)
81 # prepare beparams dict
82 for parameter in constants.BES_PARAMETERS:
83 if parameter not in beparams:
84 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
85 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
87 # prepare hvparams dict
88 for hv in constants.HYPER_TYPES:
89 if hv not in hvparams:
91 for parameter in constants.HVC_DEFAULTS[hv]:
92 if parameter not in hvparams[hv]:
93 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
94 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97 if hv not in constants.HYPER_TYPES:
98 ToStderr("invalid hypervisor: %s", hv)
101 bootstrap.InitCluster(cluster_name=args[0],
102 secondary_ip=opts.secondary_ip,
104 mac_prefix=opts.mac_prefix,
105 def_bridge=opts.def_bridge,
106 master_netdev=opts.master_netdev,
107 file_storage_dir=opts.file_storage_dir,
108 enabled_hypervisors=hvlist,
109 default_hypervisor=opts.default_hypervisor,
112 candidate_pool_size=opts.candidate_pool_size,
113 modify_etc_hosts=opts.modify_etc_hosts,
119 def DestroyCluster(opts, args):
120 """Destroy the cluster.
122 @param opts: the command line options selected by the user
124 @param args: should be an empty list
126 @return: the desired exit code
129 if not opts.yes_do_it:
130 ToStderr("Destroying a cluster is irreversible. If you really want"
131 " destroy this cluster, supply the --yes-do-it option.")
134 op = opcodes.OpDestroyCluster()
135 master = SubmitOpCode(op)
136 # if we reached this, the opcode didn't fail; we can proceed to
137 # shutdown all the daemons
138 bootstrap.FinalizeClusterDestroy(master)
142 def RenameCluster(opts, args):
143 """Rename the cluster.
145 @param opts: the command line options selected by the user
147 @param args: should contain only one element, the new cluster name
149 @return: the desired exit code
154 usertext = ("This will rename the cluster to '%s'. If you are connected"
155 " over the network to the cluster name, the operation is very"
156 " dangerous as the IP address will be removed from the node"
157 " and the change may not go through. Continue?") % name
158 if not AskUser(usertext):
161 op = opcodes.OpRenameCluster(name=name)
166 def RedistributeConfig(opts, args):
167 """Forces push of the cluster configuration.
169 @param opts: the command line options selected by the user
171 @param args: empty list
173 @return: the desired exit code
176 op = opcodes.OpRedistributeConfig()
177 SubmitOrSend(op, opts)
181 def ShowClusterVersion(opts, args):
182 """Write version of ganeti software to the standard output.
184 @param opts: the command line options selected by the user
186 @param args: should be an empty list
188 @return: the desired exit code
192 result = cl.QueryClusterInfo()
193 ToStdout("Software version: %s", result["software_version"])
194 ToStdout("Internode protocol: %s", result["protocol_version"])
195 ToStdout("Configuration format: %s", result["config_version"])
196 ToStdout("OS api version: %s", result["os_api_version"])
197 ToStdout("Export interface: %s", result["export_version"])
201 def ShowClusterMaster(opts, args):
202 """Write name of master node to the standard output.
204 @param opts: the command line options selected by the user
206 @param args: should be an empty list
208 @return: the desired exit code
211 master = bootstrap.GetMaster()
216 def ShowClusterConfig(opts, args):
217 """Shows cluster information.
219 @param opts: the command line options selected by the user
221 @param args: should be an empty list
223 @return: the desired exit code
227 result = cl.QueryClusterInfo()
229 ToStdout("Cluster name: %s", result["name"])
231 ToStdout("Master node: %s", result["master"])
233 ToStdout("Architecture (this node): %s (%s)",
234 result["architecture"][0], result["architecture"][1])
236 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
237 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
239 ToStdout("Hypervisor parameters:")
240 for hv_name, hv_dict in result["hvparams"].items():
241 ToStdout(" - %s:", hv_name)
242 for item, val in hv_dict.iteritems():
243 ToStdout(" %s: %s", item, val)
245 ToStdout("Cluster parameters:")
246 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
247 ToStdout(" - master netdev: %s", result["master_netdev"])
248 ToStdout(" - default bridge: %s", result["default_bridge"])
249 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
250 ToStdout(" - file storage path: %s", result["file_storage_dir"])
252 ToStdout("Default instance parameters:")
253 for gr_name, gr_dict in result["beparams"].items():
254 ToStdout(" - %s:", gr_name)
255 for item, val in gr_dict.iteritems():
256 ToStdout(" %s: %s", item, val)
261 def ClusterCopyFile(opts, args):
262 """Copy a file from master to some nodes.
264 @param opts: the command line options selected by the user
266 @param args: should contain only one element, the path of
267 the file to be copied
269 @return: the desired exit code
273 if not os.path.exists(filename):
274 raise errors.OpPrereqError("No such filename '%s'" % filename)
278 myname = utils.HostInfo().name
280 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
282 results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
283 results = [name for name in results if name != myname]
285 srun = ssh.SshRunner(cluster_name=cluster_name)
287 if not srun.CopyFileToNode(node, filename):
288 ToStderr("Copy of file %s to node %s failed", filename, node)
293 def RunClusterCommand(opts, args):
294 """Run a command on some nodes.
296 @param opts: the command line options selected by the user
298 @param args: should contain the command to be run and its arguments
300 @return: the desired exit code
305 command = " ".join(args)
307 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
309 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
312 srun = ssh.SshRunner(cluster_name=cluster_name)
314 # Make sure master node is at list end
315 if master_node in nodes:
316 nodes.remove(master_node)
317 nodes.append(master_node)
320 result = srun.Run(name, "root", command)
321 ToStdout("------------------------------------------------")
322 ToStdout("node: %s", name)
323 ToStdout("%s", result.output)
324 ToStdout("return code = %s", result.exit_code)
329 def VerifyCluster(opts, args):
330 """Verify integrity of cluster, performing various test on nodes.
332 @param opts: the command line options selected by the user
334 @param args: should be an empty list
336 @return: the desired exit code
340 if opts.skip_nplusone_mem:
341 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
342 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
349 def VerifyDisks(opts, args):
350 """Verify integrity of cluster disks.
352 @param opts: the command line options selected by the user
354 @param args: should be an empty list
356 @return: the desired exit code
359 op = opcodes.OpVerifyDisks()
360 result = SubmitOpCode(op)
361 if not isinstance(result, (list, tuple)) or len(result) != 4:
362 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
364 nodes, nlvm, instances, missing = result
367 ToStdout("Nodes unreachable or with bad data:")
369 ToStdout("\t%s", name)
370 retcode = constants.EXIT_SUCCESS
373 for node, text in nlvm.iteritems():
374 ToStdout("Error on node %s: LVM error: %s",
375 node, utils.SafeEncode(text[-400:]))
377 ToStdout("You need to fix these nodes first before fixing instances")
380 for iname in instances:
383 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
385 ToStdout("Activating disks for instance '%s'", iname)
387 except errors.GenericError, err:
388 nret, msg = FormatError(err)
390 ToStderr("Error activating disks for instance %s: %s", iname, msg)
393 for iname, ival in missing.iteritems():
394 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
396 ToStdout("Instance %s cannot be verified as it lives on"
397 " broken nodes", iname)
399 ToStdout("Instance %s has missing logical volumes:", iname)
401 for node, vol in ival:
403 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
405 ToStdout("\t%s /dev/xenvg/%s", node, vol)
406 ToStdout("You need to run replace_disks for all the above"
407 " instances, if this message persist after fixing nodes.")
414 def MasterFailover(opts, args):
415 """Failover the master node.
417 This command, when run on a non-master node, will cause the current
418 master to cease being master, and the non-master to become new
421 @param opts: the command line options selected by the user
423 @param args: should be an empty list
425 @return: the desired exit code
428 return bootstrap.MasterFailover()
431 def SearchTags(opts, args):
432 """Searches the tags on all the cluster.
434 @param opts: the command line options selected by the user
436 @param args: should contain only one element, the tag pattern
438 @return: the desired exit code
441 op = opcodes.OpSearchTags(pattern=args[0])
442 result = SubmitOpCode(op)
445 result = list(result)
447 for path, tag in result:
448 ToStdout("%s %s", path, tag)
451 def SetClusterParams(opts, args):
452 """Modify the cluster.
454 @param opts: the command line options selected by the user
456 @param args: should be an empty list
458 @return: the desired exit code
461 if not (not opts.lvm_storage or opts.vg_name or
462 opts.enabled_hypervisors or opts.hvparams or
463 opts.beparams or opts.candidate_pool_size is not None):
464 ToStderr("Please give at least one of the parameters.")
467 vg_name = opts.vg_name
468 if not opts.lvm_storage and opts.vg_name:
469 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
471 elif not opts.lvm_storage:
474 hvlist = opts.enabled_hypervisors
475 if hvlist is not None:
476 hvlist = hvlist.split(",")
478 # a list of (name, dict) we can pass directly to dict() (or [])
479 hvparams = dict(opts.hvparams)
480 for hv, hv_params in hvparams.iteritems():
481 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
483 beparams = opts.beparams
484 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
486 op = opcodes.OpSetClusterParams(vg_name=vg_name,
487 enabled_hypervisors=hvlist,
490 candidate_pool_size=opts.candidate_pool_size)
495 def QueueOps(opts, args):
498 @param opts: the command line options selected by the user
500 @param args: should contain only one element, the subcommand
502 @return: the desired exit code
507 if command in ("drain", "undrain"):
508 drain_flag = command == "drain"
509 client.SetQueueDrainFlag(drain_flag)
510 elif command == "info":
511 result = client.QueryConfigValues(["drain_flag"])
516 ToStdout("The drain flag is %s" % val)
518 raise errors.OpPrereqError("Command '%s' is not valid." % command)
522 # this is an option common to more than one command, so we declare
523 # it here and reuse it
524 node_option = make_option("-n", "--node", action="append", dest="nodes",
525 help="Node to copy to (if not given, all nodes),"
526 " can be given multiple times",
527 metavar="<node>", default=[])
530 'init': (InitCluster, ARGS_ONE,
532 make_option("-s", "--secondary-ip", dest="secondary_ip",
533 help="Specify the secondary ip for this node;"
534 " if given, the entire cluster must have secondary"
536 metavar="ADDRESS", default=None),
537 make_option("-m", "--mac-prefix", dest="mac_prefix",
538 help="Specify the mac prefix for the instance IP"
539 " addresses, in the format XX:XX:XX",
541 default=constants.DEFAULT_MAC_PREFIX,),
542 make_option("-g", "--vg-name", dest="vg_name",
543 help="Specify the volume group name "
544 " (cluster-wide) for disk allocation [xenvg]",
547 make_option("-b", "--bridge", dest="def_bridge",
548 help="Specify the default bridge name (cluster-wide)"
549 " to connect the instances to [%s]" %
550 constants.DEFAULT_BRIDGE,
552 default=constants.DEFAULT_BRIDGE,),
553 make_option("--master-netdev", dest="master_netdev",
554 help="Specify the node interface (cluster-wide)"
555 " on which the master IP address will be added "
556 " [%s]" % constants.DEFAULT_BRIDGE,
558 default=constants.DEFAULT_BRIDGE,),
559 make_option("--file-storage-dir", dest="file_storage_dir",
560 help="Specify the default directory (cluster-wide)"
561 " for storing the file-based disks [%s]" %
562 constants.DEFAULT_FILE_STORAGE_DIR,
564 default=constants.DEFAULT_FILE_STORAGE_DIR,),
565 make_option("--no-lvm-storage", dest="lvm_storage",
566 help="No support for lvm based instances"
568 action="store_false", default=True,),
569 make_option("--no-etc-hosts", dest="modify_etc_hosts",
570 help="Don't modify /etc/hosts"
572 action="store_false", default=True,),
573 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
574 help="Comma-separated list of hypervisors",
575 type="string", default=None),
576 make_option("-t", "--default-hypervisor",
577 dest="default_hypervisor",
578 help="Default hypervisor to use for instance creation",
579 choices=list(constants.HYPER_TYPES),
580 default=constants.DEFAULT_ENABLED_HYPERVISOR),
581 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
582 help="Hypervisor and hypervisor options, in the"
584 " hypervisor:option=value,option=value,...",
588 keyval_option("-B", "--backend-parameters", dest="beparams",
589 type="keyval", default={},
590 help="Backend parameters"),
591 make_option("-C", "--candidate-pool-size",
592 default=constants.MASTER_POOL_SIZE_DEFAULT,
593 help="Set the candidate pool size",
594 dest="candidate_pool_size", type="int"),
596 "[opts...] <cluster_name>",
597 "Initialises a new cluster configuration"),
598 'destroy': (DestroyCluster, ARGS_NONE,
600 make_option("--yes-do-it", dest="yes_do_it",
601 help="Destroy cluster",
602 action="store_true"),
604 "", "Destroy cluster"),
605 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
607 "Renames the cluster"),
608 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
610 "Forces a push of the configuration file and ssconf files"
611 " to the nodes in the cluster"),
612 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
613 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
614 help="Skip N+1 memory redundancy tests",
618 "", "Does a check on the cluster configuration"),
619 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
620 "", "Does a check on the cluster disk status"),
621 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
622 "", "Makes the current node the master"),
623 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
624 "", "Shows the cluster version"),
625 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
626 "", "Shows the cluster master"),
627 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
628 "[-n node...] <filename>",
629 "Copies a file to all (or only some) nodes"),
630 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
631 "[-n node...] <command>",
632 "Runs a command on all (or only some) nodes"),
633 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
634 "", "Show cluster configuration"),
635 'list-tags': (ListTags, ARGS_NONE,
636 [DEBUG_OPT], "", "List the tags of the cluster"),
637 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
638 "tag...", "Add tags to the cluster"),
639 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
640 "tag...", "Remove tags from the cluster"),
641 'search-tags': (SearchTags, ARGS_ONE,
642 [DEBUG_OPT], "", "Searches the tags on all objects on"
643 " the cluster for a given pattern (regex)"),
644 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
645 "drain|undrain|info", "Change queue properties"),
646 'modify': (SetClusterParams, ARGS_NONE,
648 make_option("-g", "--vg-name", dest="vg_name",
649 help="Specify the volume group name "
650 " (cluster-wide) for disk allocation "
651 "and enable lvm based storage",
653 make_option("--no-lvm-storage", dest="lvm_storage",
654 help="Disable support for lvm based instances"
656 action="store_false", default=True,),
657 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
658 help="Comma-separated list of hypervisors",
659 type="string", default=None),
660 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
661 help="Hypervisor and hypervisor options, in the"
663 " hypervisor:option=value,option=value,...",
667 keyval_option("-B", "--backend-parameters", dest="beparams",
668 type="keyval", default={},
669 help="Backend parameters"),
670 make_option("-C", "--candidate-pool-size", default=None,
671 help="Set the candidate pool size",
672 dest="candidate_pool_size", type="int"),
675 "Alters the parameters of the cluster"),
678 if __name__ == '__main__':
679 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))