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 = opts.hvparams
74 # a list of (name, dict) we can pass directly to dict()
75 hvparams = dict(opts.hvparams)
77 # otherwise init as empty dict
80 beparams = opts.beparams
81 # check for invalid parameters
82 for parameter in beparams:
83 if parameter not in constants.BES_PARAMETERS:
84 ToStderr("Invalid backend parameter: %s", parameter)
87 # prepare beparams dict
88 for parameter in constants.BES_PARAMETERS:
89 if parameter not in beparams:
90 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
91 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
93 # prepare hvparams dict
94 for hv in constants.HYPER_TYPES:
95 if hv not in hvparams:
97 for parameter in constants.HVC_DEFAULTS[hv]:
98 if parameter not in hvparams[hv]:
99 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
100 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
103 if hv not in constants.HYPER_TYPES:
104 ToStderr("invalid hypervisor: %s", hv)
107 bootstrap.InitCluster(cluster_name=args[0],
108 secondary_ip=opts.secondary_ip,
110 mac_prefix=opts.mac_prefix,
111 def_bridge=opts.def_bridge,
112 master_netdev=opts.master_netdev,
113 file_storage_dir=opts.file_storage_dir,
114 enabled_hypervisors=hvlist,
115 default_hypervisor=opts.default_hypervisor,
118 candidate_pool_size=opts.candidate_pool_size,
124 def DestroyCluster(opts, args):
125 """Destroy the cluster.
127 @param opts: the command line options selected by the user
129 @param args: should be an empty list
131 @return: the desired exit code
134 if not opts.yes_do_it:
135 ToStderr("Destroying a cluster is irreversible. If you really want"
136 " destroy this cluster, supply the --yes-do-it option.")
139 op = opcodes.OpDestroyCluster()
140 master = SubmitOpCode(op)
141 # if we reached this, the opcode didn't fail; we can proceed to
142 # shutdown all the daemons
143 bootstrap.FinalizeClusterDestroy(master)
147 def RenameCluster(opts, args):
148 """Rename the cluster.
150 @param opts: the command line options selected by the user
152 @param args: should contain only one element, the new cluster name
154 @return: the desired exit code
159 usertext = ("This will rename the cluster to '%s'. If you are connected"
160 " over the network to the cluster name, the operation is very"
161 " dangerous as the IP address will be removed from the node"
162 " and the change may not go through. Continue?") % name
163 if not AskUser(usertext):
166 op = opcodes.OpRenameCluster(name=name)
171 def RedistributeConfig(opts, args):
172 """Forces push of the cluster configuration.
174 @param opts: the command line options selected by the user
176 @param args: empty list
178 @return: the desired exit code
181 op = opcodes.OpRedistributeConfig()
182 SubmitOrSend(op, opts)
186 def ShowClusterVersion(opts, args):
187 """Write version of ganeti software to the standard output.
189 @param opts: the command line options selected by the user
191 @param args: should be an empty list
193 @return: the desired exit code
197 result = cl.QueryClusterInfo()
198 ToStdout("Software version: %s", result["software_version"])
199 ToStdout("Internode protocol: %s", result["protocol_version"])
200 ToStdout("Configuration format: %s", result["config_version"])
201 ToStdout("OS api version: %s", result["os_api_version"])
202 ToStdout("Export interface: %s", result["export_version"])
206 def ShowClusterMaster(opts, args):
207 """Write name of master node to the standard output.
209 @param opts: the command line options selected by the user
211 @param args: should be an empty list
213 @return: the desired exit code
216 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
220 def ShowClusterConfig(opts, args):
221 """Shows cluster information.
223 @param opts: the command line options selected by the user
225 @param args: should be an empty list
227 @return: the desired exit code
231 result = cl.QueryClusterInfo()
233 ToStdout("Cluster name: %s", result["name"])
235 ToStdout("Master node: %s", result["master"])
237 ToStdout("Architecture (this node): %s (%s)",
238 result["architecture"][0], result["architecture"][1])
240 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
241 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
243 ToStdout("Hypervisor parameters:")
244 for hv_name, hv_dict in result["hvparams"].items():
245 ToStdout(" - %s:", hv_name)
246 for item, val in hv_dict.iteritems():
247 ToStdout(" %s: %s", item, val)
249 ToStdout("Cluster parameters:")
250 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
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.")
472 hvlist = opts.enabled_hypervisors
473 if hvlist is not None:
474 hvlist = hvlist.split(",")
476 hvparams = opts.hvparams
478 # a list of (name, dict) we can pass directly to dict()
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=opts.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)
519 # this is an option common to more than one command, so we declare
520 # it here and reuse it
521 node_option = make_option("-n", "--node", action="append", dest="nodes",
522 help="Node to copy to (if not given, all nodes),"
523 " can be given multiple times",
524 metavar="<node>", default=[])
527 'init': (InitCluster, ARGS_ONE,
529 make_option("-s", "--secondary-ip", dest="secondary_ip",
530 help="Specify the secondary ip for this node;"
531 " if given, the entire cluster must have secondary"
533 metavar="ADDRESS", default=None),
534 make_option("-m", "--mac-prefix", dest="mac_prefix",
535 help="Specify the mac prefix for the instance IP"
536 " addresses, in the format XX:XX:XX",
538 default=constants.DEFAULT_MAC_PREFIX,),
539 make_option("-g", "--vg-name", dest="vg_name",
540 help="Specify the volume group name "
541 " (cluster-wide) for disk allocation [xenvg]",
544 make_option("-b", "--bridge", dest="def_bridge",
545 help="Specify the default bridge name (cluster-wide)"
546 " to connect the instances to [%s]" %
547 constants.DEFAULT_BRIDGE,
549 default=constants.DEFAULT_BRIDGE,),
550 make_option("--master-netdev", dest="master_netdev",
551 help="Specify the node interface (cluster-wide)"
552 " on which the master IP address will be added "
553 " [%s]" % constants.DEFAULT_BRIDGE,
555 default=constants.DEFAULT_BRIDGE,),
556 make_option("--file-storage-dir", dest="file_storage_dir",
557 help="Specify the default directory (cluster-wide)"
558 " for storing the file-based disks [%s]" %
559 constants.DEFAULT_FILE_STORAGE_DIR,
561 default=constants.DEFAULT_FILE_STORAGE_DIR,),
562 make_option("--no-lvm-storage", dest="lvm_storage",
563 help="No support for lvm based instances"
565 action="store_false", default=True,),
566 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
567 help="Comma-separated list of hypervisors",
568 type="string", default=None),
569 make_option("-t", "--default-hypervisor",
570 dest="default_hypervisor",
571 help="Default hypervisor to use for instance creation",
572 choices=list(constants.HYPER_TYPES),
573 default=constants.DEFAULT_ENABLED_HYPERVISOR),
574 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
575 help="Hypervisor and hypervisor options, in the"
577 " hypervisor:option=value,option=value,...",
581 keyval_option("-B", "--backend-parameters", dest="beparams",
582 type="keyval", default={},
583 help="Backend parameters"),
584 make_option("-C", "--candidate-pool-size",
585 default=constants.MASTER_POOL_SIZE_DEFAULT,
586 help="Set the candidate pool size",
587 dest="candidate_pool_size", type="int"),
589 "[opts...] <cluster_name>",
590 "Initialises a new cluster configuration"),
591 'destroy': (DestroyCluster, ARGS_NONE,
593 make_option("--yes-do-it", dest="yes_do_it",
594 help="Destroy cluster",
595 action="store_true"),
597 "", "Destroy cluster"),
598 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
600 "Renames the cluster"),
601 'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
603 "Forces a push of the configuration file and ssconf files"
604 " to the nodes in the cluster"),
605 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
606 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
607 help="Skip N+1 memory redundancy tests",
611 "", "Does a check on the cluster configuration"),
612 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
613 "", "Does a check on the cluster disk status"),
614 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
615 "", "Makes the current node the master"),
616 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
617 "", "Shows the cluster version"),
618 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
619 "", "Shows the cluster master"),
620 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
621 "[-n node...] <filename>",
622 "Copies a file to all (or only some) nodes"),
623 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
624 "[-n node...] <command>",
625 "Runs a command on all (or only some) nodes"),
626 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
627 "", "Show cluster configuration"),
628 'list-tags': (ListTags, ARGS_NONE,
629 [DEBUG_OPT], "", "List the tags of the cluster"),
630 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
631 "tag...", "Add tags to the cluster"),
632 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
633 "tag...", "Remove tags from the cluster"),
634 'search-tags': (SearchTags, ARGS_ONE,
635 [DEBUG_OPT], "", "Searches the tags on all objects on"
636 " the cluster for a given pattern (regex)"),
637 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
638 "drain|undrain|info", "Change queue properties"),
639 'modify': (SetClusterParams, ARGS_NONE,
641 make_option("-g", "--vg-name", dest="vg_name",
642 help="Specify the volume group name "
643 " (cluster-wide) for disk allocation "
644 "and enable lvm based storage",
646 make_option("--no-lvm-storage", dest="lvm_storage",
647 help="Disable support for lvm based instances"
649 action="store_false", default=True,),
650 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
651 help="Comma-separated list of hypervisors",
652 type="string", default=None),
653 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
654 help="Hypervisor and hypervisor options, in the"
656 " hypervisor:option=value,option=value,...",
660 keyval_option("-B", "--backend-parameters", dest="beparams",
661 type="keyval", default={},
662 help="Backend parameters"),
663 make_option("-C", "--candidate-pool-size", default=None,
664 help="Set the candidate pool size",
665 dest="candidate_pool_size", type="int"),
668 "Alters the parameters of the cluster"),
671 if __name__ == '__main__':
672 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))