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 = [constants.DEFAULT_ENABLED_HYPERVISOR]
65 # avoid an impossible situation
66 if opts.default_hypervisor in hvlist:
67 default_hypervisor = opts.default_hypervisor
69 default_hypervisor = hvlist[0]
71 hvparams = opts.hvparams
73 # a list of (name, dict) we can pass directly to dict()
74 hvparams = dict(opts.hvparams)
76 # otherwise init as empty dict
79 beparams = opts.beparams
80 # check for invalid parameters
81 for parameter in beparams:
82 if parameter not in constants.BES_PARAMETERS:
83 ToStderr("Invalid backend parameter: %s", parameter)
86 # prepare beparams dict
87 for parameter in constants.BES_PARAMETERS:
88 if parameter not in beparams:
89 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
93 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
95 ToStderr("%s must be an integer", constants.BE_VCPUS)
98 if not isinstance(beparams[constants.BE_MEMORY], int):
99 beparams[constants.BE_MEMORY] = utils.ParseUnit(
100 beparams[constants.BE_MEMORY])
102 # prepare hvparams dict
103 for hv in constants.HYPER_TYPES:
104 if hv not in hvparams:
106 for parameter in constants.HVC_DEFAULTS[hv]:
107 if parameter not in hvparams[hv]:
108 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
111 if hv not in constants.HYPER_TYPES:
112 ToStderr("invalid hypervisor: %s", hv)
115 bootstrap.InitCluster(cluster_name=args[0],
116 secondary_ip=opts.secondary_ip,
118 mac_prefix=opts.mac_prefix,
119 def_bridge=opts.def_bridge,
120 master_netdev=opts.master_netdev,
121 file_storage_dir=opts.file_storage_dir,
122 enabled_hypervisors=hvlist,
123 default_hypervisor=default_hypervisor,
130 def DestroyCluster(opts, args):
131 """Destroy the cluster.
133 @param opts: the command line options selected by the user
135 @param args: should be an empty list
137 @return: the desired exit code
140 if not opts.yes_do_it:
141 ToStderr("Destroying a cluster is irreversible. If you really want"
142 " destroy this cluster, supply the --yes-do-it option.")
145 op = opcodes.OpDestroyCluster()
146 master = SubmitOpCode(op)
147 # if we reached this, the opcode didn't fail; we can proceed to
148 # shutdown all the daemons
149 bootstrap.FinalizeClusterDestroy(master)
153 def RenameCluster(opts, args):
154 """Rename the cluster.
156 @param opts: the command line options selected by the user
158 @param args: should contain only one element, the new cluster name
160 @return: the desired exit code
165 usertext = ("This will rename the cluster to '%s'. If you are connected"
166 " over the network to the cluster name, the operation is very"
167 " dangerous as the IP address will be removed from the node"
168 " and the change may not go through. Continue?") % name
169 if not AskUser(usertext):
172 op = opcodes.OpRenameCluster(name=name)
177 def ShowClusterVersion(opts, args):
178 """Write version of ganeti software to the standard output.
180 @param opts: the command line options selected by the user
182 @param args: should be an empty list
184 @return: the desired exit code
187 op = opcodes.OpQueryClusterInfo()
188 result = SubmitOpCode(op)
189 ToStdout("Software version: %s", result["software_version"])
190 ToStdout("Internode protocol: %s", result["protocol_version"])
191 ToStdout("Configuration format: %s", result["config_version"])
192 ToStdout("OS api version: %s", result["os_api_version"])
193 ToStdout("Export interface: %s", result["export_version"])
197 def ShowClusterMaster(opts, args):
198 """Write name of master node to the standard output.
200 @param opts: the command line options selected by the user
202 @param args: should be an empty list
204 @return: the desired exit code
207 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
211 def ShowClusterConfig(opts, args):
212 """Shows cluster information.
214 @param opts: the command line options selected by the user
216 @param args: should be an empty list
218 @return: the desired exit code
221 op = opcodes.OpQueryClusterInfo()
222 result = SubmitOpCode(op)
224 ToStdout("Cluster name: %s", result["name"])
226 ToStdout("Master node: %s", result["master"])
228 ToStdout("Architecture (this node): %s (%s)",
229 result["architecture"][0], result["architecture"][1])
231 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
232 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
234 ToStdout("Hypervisor parameters:")
235 for hv_name, hv_dict in result["hvparams"].items():
236 ToStdout(" - %s:", hv_name)
237 for item, val in hv_dict.iteritems():
238 ToStdout(" %s: %s", item, val)
240 ToStdout("Cluster parameters:")
241 ToStdout(" - candidate pool size: %s", result["candidate_pool_size"])
243 ToStdout("Default instance parameters:")
244 for gr_name, gr_dict in result["beparams"].items():
245 ToStdout(" - %s:", gr_name)
246 for item, val in gr_dict.iteritems():
247 ToStdout(" %s: %s", item, val)
252 def ClusterCopyFile(opts, args):
253 """Copy a file from master to some nodes.
255 @param opts: the command line options selected by the user
257 @param args: should contain only one element, the path of
258 the file to be copied
260 @return: the desired exit code
264 if not os.path.exists(filename):
265 raise errors.OpPrereqError("No such filename '%s'" % filename)
269 myname = utils.HostInfo().name
271 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
273 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
274 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
276 srun = ssh.SshRunner(cluster_name=cluster_name)
278 if not srun.CopyFileToNode(node, filename):
279 ToStderr("Copy of file %s to node %s failed", filename, node)
284 def RunClusterCommand(opts, args):
285 """Run a command on some nodes.
287 @param opts: the command line options selected by the user
289 @param args: should contain the command to be run and its arguments
291 @return: the desired exit code
296 command = " ".join(args)
297 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
298 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
300 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
303 srun = ssh.SshRunner(cluster_name=cluster_name)
305 # Make sure master node is at list end
306 if master_node in nodes:
307 nodes.remove(master_node)
308 nodes.append(master_node)
311 result = srun.Run(name, "root", command)
312 ToStdout("------------------------------------------------")
313 ToStdout("node: %s", name)
314 ToStdout("%s", result.output)
315 ToStdout("return code = %s", result.exit_code)
320 def VerifyCluster(opts, args):
321 """Verify integrity of cluster, performing various test on nodes.
323 @param opts: the command line options selected by the user
325 @param args: should be an empty list
327 @return: the desired exit code
331 if opts.skip_nplusone_mem:
332 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
333 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
340 def VerifyDisks(opts, args):
341 """Verify integrity of cluster disks.
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
350 op = opcodes.OpVerifyDisks()
351 result = SubmitOpCode(op)
352 if not isinstance(result, (list, tuple)) or len(result) != 4:
353 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
355 nodes, nlvm, instances, missing = result
358 ToStdout("Nodes unreachable or with bad data:")
360 ToStdout("\t%s", name)
361 retcode = constants.EXIT_SUCCESS
364 for node, text in nlvm.iteritems():
365 ToStdout("Error on node %s: LVM error: %s",
366 node, text[-400:].encode('string_escape'))
368 ToStdout("You need to fix these nodes first before fixing instances")
371 for iname in instances:
374 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
376 ToStdout("Activating disks for instance '%s'", iname)
378 except errors.GenericError, err:
379 nret, msg = FormatError(err)
381 ToStderr("Error activating disks for instance %s: %s", iname, msg)
384 for iname, ival in missing.iteritems():
385 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
387 ToStdout("Instance %s cannot be verified as it lives on"
388 " broken nodes", iname)
390 ToStdout("Instance %s has missing logical volumes:", iname)
392 for node, vol in ival:
394 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
396 ToStdout("\t%s /dev/xenvg/%s", node, vol)
397 ToStdout("You need to run replace_disks for all the above"
398 " instances, if this message persist after fixing nodes.")
405 def MasterFailover(opts, args):
406 """Failover the master node.
408 This command, when run on a non-master node, will cause the current
409 master to cease being master, and the non-master to become new
412 @param opts: the command line options selected by the user
414 @param args: should be an empty list
416 @return: the desired exit code
419 return bootstrap.MasterFailover()
422 def SearchTags(opts, args):
423 """Searches the tags on all the cluster.
425 @param opts: the command line options selected by the user
427 @param args: should contain only one element, the tag pattern
429 @return: the desired exit code
432 op = opcodes.OpSearchTags(pattern=args[0])
433 result = SubmitOpCode(op)
436 result = list(result)
438 for path, tag in result:
439 ToStdout("%s %s", path, tag)
442 def SetClusterParams(opts, args):
443 """Modify the cluster.
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
452 if not (not opts.lvm_storage or opts.vg_name or
453 opts.enabled_hypervisors or opts.hvparams or
454 opts.beparams or opts.candidate_pool_size is not None):
455 ToStderr("Please give at least one of the parameters.")
458 vg_name = opts.vg_name
459 if not opts.lvm_storage and opts.vg_name:
460 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
463 hvlist = opts.enabled_hypervisors
464 if hvlist is not None:
465 hvlist = hvlist.split(",")
467 hvparams = opts.hvparams
469 # a list of (name, dict) we can pass directly to dict()
470 hvparams = dict(opts.hvparams)
472 beparams = opts.beparams
474 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
475 enabled_hypervisors=hvlist,
478 candidate_pool_size=opts.candidate_pool_size)
483 def QueueOps(opts, args):
486 @param opts: the command line options selected by the user
488 @param args: should contain only one element, the subcommand
490 @return: the desired exit code
495 if command in ("drain", "undrain"):
496 drain_flag = command == "drain"
497 client.SetQueueDrainFlag(drain_flag)
498 elif command == "info":
499 result = client.QueryConfigValues(["drain_flag"])
504 ToStdout("The drain flag is %s" % val)
507 # this is an option common to more than one command, so we declare
508 # it here and reuse it
509 node_option = make_option("-n", "--node", action="append", dest="nodes",
510 help="Node to copy to (if not given, all nodes),"
511 " can be given multiple times",
512 metavar="<node>", default=[])
515 'init': (InitCluster, ARGS_ONE,
517 make_option("-s", "--secondary-ip", dest="secondary_ip",
518 help="Specify the secondary ip for this node;"
519 " if given, the entire cluster must have secondary"
521 metavar="ADDRESS", default=None),
522 make_option("-m", "--mac-prefix", dest="mac_prefix",
523 help="Specify the mac prefix for the instance IP"
524 " addresses, in the format XX:XX:XX",
526 default="aa:00:00",),
527 make_option("-g", "--vg-name", dest="vg_name",
528 help="Specify the volume group name "
529 " (cluster-wide) for disk allocation [xenvg]",
532 make_option("-b", "--bridge", dest="def_bridge",
533 help="Specify the default bridge name (cluster-wide)"
534 " to connect the instances to [%s]" %
535 constants.DEFAULT_BRIDGE,
537 default=constants.DEFAULT_BRIDGE,),
538 make_option("--master-netdev", dest="master_netdev",
539 help="Specify the node interface (cluster-wide)"
540 " on which the master IP address will be added "
541 " [%s]" % constants.DEFAULT_BRIDGE,
543 default=constants.DEFAULT_BRIDGE,),
544 make_option("--file-storage-dir", dest="file_storage_dir",
545 help="Specify the default directory (cluster-wide)"
546 " for storing the file-based disks [%s]" %
547 constants.DEFAULT_FILE_STORAGE_DIR,
549 default=constants.DEFAULT_FILE_STORAGE_DIR,),
550 make_option("--no-lvm-storage", dest="lvm_storage",
551 help="No support for lvm based instances"
553 action="store_false", default=True,),
554 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
555 help="Comma-separated list of hypervisors",
556 type="string", default=None),
557 make_option("-t", "--default-hypervisor",
558 dest="default_hypervisor",
559 help="Default hypervisor to use for instance creation",
560 choices=list(constants.HYPER_TYPES),
561 default=constants.DEFAULT_ENABLED_HYPERVISOR),
562 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
563 help="Hypervisor and hypervisor options, in the"
565 " hypervisor:option=value,option=value,...",
569 keyval_option("-B", "--backend-parameters", dest="beparams",
570 type="keyval", default={},
571 help="Backend parameters"),
573 "[opts...] <cluster_name>",
574 "Initialises a new cluster configuration"),
575 'destroy': (DestroyCluster, ARGS_NONE,
577 make_option("--yes-do-it", dest="yes_do_it",
578 help="Destroy cluster",
579 action="store_true"),
581 "", "Destroy cluster"),
582 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
584 "Renames the cluster"),
585 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
586 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
587 help="Skip N+1 memory redundancy tests",
591 "", "Does a check on the cluster configuration"),
592 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
593 "", "Does a check on the cluster disk status"),
594 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
595 "", "Makes the current node the master"),
596 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
597 "", "Shows the cluster version"),
598 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
599 "", "Shows the cluster master"),
600 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
601 "[-n node...] <filename>",
602 "Copies a file to all (or only some) nodes"),
603 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
604 "[-n node...] <command>",
605 "Runs a command on all (or only some) nodes"),
606 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
607 "", "Show cluster configuration"),
608 'list-tags': (ListTags, ARGS_NONE,
609 [DEBUG_OPT], "", "List the tags of the cluster"),
610 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
611 "tag...", "Add tags to the cluster"),
612 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
613 "tag...", "Remove tags from the cluster"),
614 'search-tags': (SearchTags, ARGS_ONE,
615 [DEBUG_OPT], "", "Searches the tags on all objects on"
616 " the cluster for a given pattern (regex)"),
617 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
618 "drain|undrain|info", "Change queue properties"),
619 'modify': (SetClusterParams, ARGS_NONE,
621 make_option("-g", "--vg-name", dest="vg_name",
622 help="Specify the volume group name "
623 " (cluster-wide) for disk allocation "
624 "and enable lvm based storage",
626 make_option("--no-lvm-storage", dest="lvm_storage",
627 help="Disable support for lvm based instances"
629 action="store_false", default=True,),
630 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
631 help="Comma-separated list of hypervisors",
632 type="string", default=None),
633 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
634 help="Hypervisor and hypervisor options, in the"
636 " hypervisor:option=value,option=value,...",
640 keyval_option("-B", "--backend-parameters", dest="beparams",
641 type="keyval", default={},
642 help="Backend parameters"),
643 make_option("-C", "--candidate-pool-size", default=None,
644 help="Set the candidate pool size",
645 dest="candidate_pool_size", type="int"),
648 "Alters the parameters of the cluster"),
651 if __name__ == '__main__':
652 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))