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
39 def InitCluster(opts, args):
40 """Initialize the cluster.
42 @param opts: the command line options selected by the user
44 @param args: should contain only one element, the desired
47 @return: the desired exit code
50 if not opts.lvm_storage and opts.vg_name:
51 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
54 vg_name = opts.vg_name
55 if opts.lvm_storage and not opts.vg_name:
56 vg_name = constants.DEFAULT_VG
58 hvlist = opts.enabled_hypervisors
59 if hvlist is not None:
60 hvlist = hvlist.split(",")
62 hvlist = [constants.DEFAULT_ENABLED_HYPERVISOR]
64 # avoid an impossible situation
65 if opts.default_hypervisor in hvlist:
66 default_hypervisor = opts.default_hypervisor
68 default_hypervisor = hvlist[0]
70 hvparams = opts.hvparams
72 # a list of (name, dict) we can pass directly to dict()
73 hvparams = dict(opts.hvparams)
75 # otherwise init as empty dict
78 beparams = opts.beparams
79 # check for invalid parameters
80 for parameter in beparams:
81 if parameter not in constants.BES_PARAMETERS:
82 ToStderr("Invalid backend parameter: %s", parameter)
85 # prepare beparams dict
86 for parameter in constants.BES_PARAMETERS:
87 if parameter not in beparams:
88 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
92 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
94 ToStderr("%s must be an integer", constants.BE_VCPUS)
97 if not isinstance(beparams[constants.BE_MEMORY], int):
98 beparams[constants.BE_MEMORY] = utils.ParseUnit(
99 beparams[constants.BE_MEMORY])
101 # prepare hvparams dict
102 for hv in constants.HYPER_TYPES:
103 if hv not in hvparams:
105 for parameter in constants.HVC_DEFAULTS[hv]:
106 if parameter not in hvparams[hv]:
107 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
110 if hv not in constants.HYPER_TYPES:
111 ToStderr("invalid hypervisor: %s", hv)
114 bootstrap.InitCluster(cluster_name=args[0],
115 secondary_ip=opts.secondary_ip,
117 mac_prefix=opts.mac_prefix,
118 def_bridge=opts.def_bridge,
119 master_netdev=opts.master_netdev,
120 file_storage_dir=opts.file_storage_dir,
121 enabled_hypervisors=hvlist,
122 default_hypervisor=default_hypervisor,
128 def DestroyCluster(opts, args):
129 """Destroy the cluster.
131 @param opts: the command line options selected by the user
133 @param args: should be an empty list
135 @return: the desired exit code
138 if not opts.yes_do_it:
139 ToStderr("Destroying a cluster is irreversible. If you really want"
140 " destroy this cluster, supply the --yes-do-it option.")
143 op = opcodes.OpDestroyCluster()
144 master = SubmitOpCode(op)
145 # if we reached this, the opcode didn't fail; we can proceed to
146 # shutdown all the daemons
147 bootstrap.FinalizeClusterDestroy(master)
151 def RenameCluster(opts, args):
152 """Rename the cluster.
154 @param opts: the command line options selected by the user
156 @param args: should contain only one element, the new cluster name
158 @return: the desired exit code
163 usertext = ("This will rename the cluster to '%s'. If you are connected"
164 " over the network to the cluster name, the operation is very"
165 " dangerous as the IP address will be removed from the node"
166 " and the change may not go through. Continue?") % name
167 if not AskUser(usertext):
170 op = opcodes.OpRenameCluster(name=name)
175 def ShowClusterVersion(opts, args):
176 """Write version of ganeti software to the standard output.
178 @param opts: the command line options selected by the user
180 @param args: should be an empty list
182 @return: the desired exit code
185 op = opcodes.OpQueryClusterInfo()
186 result = SubmitOpCode(op)
187 ToStdout("Software version: %s", result["software_version"])
188 ToStdout("Internode protocol: %s", result["protocol_version"])
189 ToStdout("Configuration format: %s", result["config_version"])
190 ToStdout("OS api version: %s", result["os_api_version"])
191 ToStdout("Export interface: %s", result["export_version"])
195 def ShowClusterMaster(opts, args):
196 """Write name of master node to the standard output.
198 @param opts: the command line options selected by the user
200 @param args: should be an empty list
202 @return: the desired exit code
205 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
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
219 op = opcodes.OpQueryClusterInfo()
220 result = SubmitOpCode(op)
222 ToStdout("Cluster name: %s", result["name"])
224 ToStdout("Master node: %s", result["master"])
226 ToStdout("Architecture (this node): %s (%s)",
227 result["architecture"][0], result["architecture"][1])
229 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
230 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
232 ToStdout("Hypervisor parameters:")
233 for hv_name, hv_dict in result["hvparams"].items():
234 ToStdout(" - %s:", hv_name)
235 for item, val in hv_dict.iteritems():
236 ToStdout(" %s: %s", item, val)
238 ToStdout("Cluster parameters:")
239 for gr_name, gr_dict in result["beparams"].items():
240 ToStdout(" - %s:", gr_name)
241 for item, val in gr_dict.iteritems():
242 ToStdout(" %s: %s", item, val)
247 def ClusterCopyFile(opts, args):
248 """Copy a file from master to some nodes.
250 @param opts: the command line options selected by the user
252 @param args: should contain only one element, the path of
253 the file to be copied
255 @return: the desired exit code
259 if not os.path.exists(filename):
260 raise errors.OpPrereqError("No such filename '%s'" % filename)
264 myname = utils.HostInfo().name
266 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
268 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
269 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
271 srun = ssh.SshRunner(cluster_name=cluster_name)
273 if not srun.CopyFileToNode(node, filename):
274 ToStderr("Copy of file %s to node %s failed", filename, node)
279 def RunClusterCommand(opts, args):
280 """Run a command on some nodes.
282 @param opts: the command line options selected by the user
284 @param args: should contain the command to be run and its arguments
286 @return: the desired exit code
291 command = " ".join(args)
292 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
293 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
295 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
298 srun = ssh.SshRunner(cluster_name=cluster_name)
300 # Make sure master node is at list end
301 if master_node in nodes:
302 nodes.remove(master_node)
303 nodes.append(master_node)
306 result = srun.Run(name, "root", command)
307 ToStdout("------------------------------------------------")
308 ToStdout("node: %s", name)
309 ToStdout("%s", result.output)
310 ToStdout("return code = %s", result.exit_code)
315 def VerifyCluster(opts, args):
316 """Verify integrity of cluster, performing various test on nodes.
318 @param opts: the command line options selected by the user
320 @param args: should be an empty list
322 @return: the desired exit code
326 if opts.skip_nplusone_mem:
327 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
328 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
335 def VerifyDisks(opts, args):
336 """Verify integrity of cluster disks.
338 @param opts: the command line options selected by the user
340 @param args: should be an empty list
342 @return: the desired exit code
345 op = opcodes.OpVerifyDisks()
346 result = SubmitOpCode(op)
347 if not isinstance(result, (list, tuple)) or len(result) != 4:
348 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
350 nodes, nlvm, instances, missing = result
353 ToStdout("Nodes unreachable or with bad data:")
355 ToStdout("\t%s", name)
356 retcode = constants.EXIT_SUCCESS
359 for node, text in nlvm.iteritems():
360 ToStdout("Error on node %s: LVM error: %s",
361 node, text[-400:].encode('string_escape'))
363 ToStdout("You need to fix these nodes first before fixing instances")
366 for iname in instances:
369 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
371 ToStdout("Activating disks for instance '%s'", iname)
373 except errors.GenericError, err:
374 nret, msg = FormatError(err)
376 ToStderr("Error activating disks for instance %s: %s", iname, msg)
379 for iname, ival in missing.iteritems():
380 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
382 ToStdout("Instance %s cannot be verified as it lives on"
383 " broken nodes", iname)
385 ToStdout("Instance %s has missing logical volumes:", iname)
387 for node, vol in ival:
389 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
391 ToStdout("\t%s /dev/xenvg/%s", node, vol)
392 ToStdout("You need to run replace_disks for all the above"
393 " instances, if this message persist after fixing nodes.")
399 def MasterFailover(opts, args):
400 """Failover the master node.
402 This command, when run on a non-master node, will cause the current
403 master to cease being master, and the non-master to become new
406 @param opts: the command line options selected by the user
408 @param args: should be an empty list
410 @return: the desired exit code
413 return bootstrap.MasterFailover()
416 def SearchTags(opts, args):
417 """Searches the tags on all the cluster.
419 @param opts: the command line options selected by the user
421 @param args: should contain only one element, the tag pattern
423 @return: the desired exit code
426 op = opcodes.OpSearchTags(pattern=args[0])
427 result = SubmitOpCode(op)
430 result = list(result)
432 for path, tag in result:
433 ToStdout("%s %s", path, tag)
436 def SetClusterParams(opts, args):
437 """Modify the cluster.
439 @param opts: the command line options selected by the user
441 @param args: should be an empty list
443 @return: the desired exit code
446 if not (not opts.lvm_storage or opts.vg_name or
447 opts.enabled_hypervisors or opts.hvparams or
449 ToStderr("Please give at least one of the parameters.")
452 vg_name = opts.vg_name
453 if not opts.lvm_storage and opts.vg_name:
454 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
457 hvlist = opts.enabled_hypervisors
458 if hvlist is not None:
459 hvlist = hvlist.split(",")
461 hvparams = opts.hvparams
463 # a list of (name, dict) we can pass directly to dict()
464 hvparams = dict(opts.hvparams)
466 beparams = opts.beparams
468 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
469 enabled_hypervisors=hvlist,
476 def QueueOps(opts, args):
479 @param opts: the command line options selected by the user
481 @param args: should contain only one element, the subcommand
483 @return: the desired exit code
488 if command in ("drain", "undrain"):
489 drain_flag = command == "drain"
490 client.SetQueueDrainFlag(drain_flag)
491 elif command == "info":
492 result = client.QueryConfigValues(["drain_flag"])
497 ToStdout("The drain flag is %s" % val)
500 # this is an option common to more than one command, so we declare
501 # it here and reuse it
502 node_option = make_option("-n", "--node", action="append", dest="nodes",
503 help="Node to copy to (if not given, all nodes),"
504 " can be given multiple times",
505 metavar="<node>", default=[])
508 'init': (InitCluster, ARGS_ONE,
510 make_option("-s", "--secondary-ip", dest="secondary_ip",
511 help="Specify the secondary ip for this node;"
512 " if given, the entire cluster must have secondary"
514 metavar="ADDRESS", default=None),
515 make_option("-m", "--mac-prefix", dest="mac_prefix",
516 help="Specify the mac prefix for the instance IP"
517 " addresses, in the format XX:XX:XX",
519 default="aa:00:00",),
520 make_option("-g", "--vg-name", dest="vg_name",
521 help="Specify the volume group name "
522 " (cluster-wide) for disk allocation [xenvg]",
525 make_option("-b", "--bridge", dest="def_bridge",
526 help="Specify the default bridge name (cluster-wide)"
527 " to connect the instances to [%s]" %
528 constants.DEFAULT_BRIDGE,
530 default=constants.DEFAULT_BRIDGE,),
531 make_option("--master-netdev", dest="master_netdev",
532 help="Specify the node interface (cluster-wide)"
533 " on which the master IP address will be added "
534 " [%s]" % constants.DEFAULT_BRIDGE,
536 default=constants.DEFAULT_BRIDGE,),
537 make_option("--file-storage-dir", dest="file_storage_dir",
538 help="Specify the default directory (cluster-wide)"
539 " for storing the file-based disks [%s]" %
540 constants.DEFAULT_FILE_STORAGE_DIR,
542 default=constants.DEFAULT_FILE_STORAGE_DIR,),
543 make_option("--no-lvm-storage", dest="lvm_storage",
544 help="No support for lvm based instances"
546 action="store_false", default=True,),
547 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
548 help="Comma-separated list of hypervisors",
549 type="string", default=None),
550 make_option("-t", "--default-hypervisor",
551 dest="default_hypervisor",
552 help="Default hypervisor to use for instance creation",
553 choices=list(constants.HYPER_TYPES),
554 default=constants.DEFAULT_ENABLED_HYPERVISOR),
555 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
556 help="Hypervisor and hypervisor options, in the"
558 " hypervisor:option=value,option=value,...",
562 keyval_option("-B", "--backend-parameters", dest="beparams",
563 type="keyval", default={},
564 help="Backend parameters"),
566 "[opts...] <cluster_name>",
567 "Initialises a new cluster configuration"),
568 'destroy': (DestroyCluster, ARGS_NONE,
570 make_option("--yes-do-it", dest="yes_do_it",
571 help="Destroy cluster",
572 action="store_true"),
574 "", "Destroy cluster"),
575 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
577 "Renames the cluster"),
578 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
579 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
580 help="Skip N+1 memory redundancy tests",
584 "", "Does a check on the cluster configuration"),
585 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
586 "", "Does a check on the cluster disk status"),
587 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
588 "", "Makes the current node the master"),
589 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
590 "", "Shows the cluster version"),
591 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
592 "", "Shows the cluster master"),
593 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
594 "[-n node...] <filename>",
595 "Copies a file to all (or only some) nodes"),
596 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
597 "[-n node...] <command>",
598 "Runs a command on all (or only some) nodes"),
599 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
600 "", "Show cluster configuration"),
601 'list-tags': (ListTags, ARGS_NONE,
602 [DEBUG_OPT], "", "List the tags of the cluster"),
603 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
604 "tag...", "Add tags to the cluster"),
605 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
606 "tag...", "Remove tags from the cluster"),
607 'search-tags': (SearchTags, ARGS_ONE,
608 [DEBUG_OPT], "", "Searches the tags on all objects on"
609 " the cluster for a given pattern (regex)"),
610 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
611 "drain|undrain|info", "Change queue properties"),
612 'modify': (SetClusterParams, ARGS_NONE,
614 make_option("-g", "--vg-name", dest="vg_name",
615 help="Specify the volume group name "
616 " (cluster-wide) for disk allocation "
617 "and enable lvm based storage",
619 make_option("--no-lvm-storage", dest="lvm_storage",
620 help="Disable support for lvm based instances"
622 action="store_false", default=True,),
623 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
624 help="Comma-separated list of hypervisors",
625 type="string", default=None),
626 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
627 help="Hypervisor and hypervisor options, in the"
629 " hypervisor:option=value,option=value,...",
633 keyval_option("-B", "--backend-parameters", dest="beparams",
634 type="keyval", default={},
635 help="Backend parameters"),
638 "Alters the parameters of the cluster"),
641 if __name__ == '__main__':
642 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))