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 for gr_name, gr_dict in result["beparams"].items():
242 ToStdout(" - %s:", gr_name)
243 for item, val in gr_dict.iteritems():
244 ToStdout(" %s: %s", item, val)
249 def ClusterCopyFile(opts, args):
250 """Copy a file from master to some nodes.
252 @param opts: the command line options selected by the user
254 @param args: should contain only one element, the path of
255 the file to be copied
257 @return: the desired exit code
261 if not os.path.exists(filename):
262 raise errors.OpPrereqError("No such filename '%s'" % filename)
266 myname = utils.HostInfo().name
268 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
270 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
271 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
273 srun = ssh.SshRunner(cluster_name=cluster_name)
275 if not srun.CopyFileToNode(node, filename):
276 ToStderr("Copy of file %s to node %s failed", filename, node)
281 def RunClusterCommand(opts, args):
282 """Run a command on some nodes.
284 @param opts: the command line options selected by the user
286 @param args: should contain the command to be run and its arguments
288 @return: the desired exit code
293 command = " ".join(args)
294 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
295 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
297 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
300 srun = ssh.SshRunner(cluster_name=cluster_name)
302 # Make sure master node is at list end
303 if master_node in nodes:
304 nodes.remove(master_node)
305 nodes.append(master_node)
308 result = srun.Run(name, "root", command)
309 ToStdout("------------------------------------------------")
310 ToStdout("node: %s", name)
311 ToStdout("%s", result.output)
312 ToStdout("return code = %s", result.exit_code)
317 def VerifyCluster(opts, args):
318 """Verify integrity of cluster, performing various test on nodes.
320 @param opts: the command line options selected by the user
322 @param args: should be an empty list
324 @return: the desired exit code
328 if opts.skip_nplusone_mem:
329 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
330 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
337 def VerifyDisks(opts, args):
338 """Verify integrity of cluster disks.
340 @param opts: the command line options selected by the user
342 @param args: should be an empty list
344 @return: the desired exit code
347 op = opcodes.OpVerifyDisks()
348 result = SubmitOpCode(op)
349 if not isinstance(result, (list, tuple)) or len(result) != 4:
350 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
352 nodes, nlvm, instances, missing = result
355 ToStdout("Nodes unreachable or with bad data:")
357 ToStdout("\t%s", name)
358 retcode = constants.EXIT_SUCCESS
361 for node, text in nlvm.iteritems():
362 ToStdout("Error on node %s: LVM error: %s",
363 node, text[-400:].encode('string_escape'))
365 ToStdout("You need to fix these nodes first before fixing instances")
368 for iname in instances:
371 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
373 ToStdout("Activating disks for instance '%s'", iname)
375 except errors.GenericError, err:
376 nret, msg = FormatError(err)
378 ToStderr("Error activating disks for instance %s: %s", iname, msg)
381 for iname, ival in missing.iteritems():
382 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
384 ToStdout("Instance %s cannot be verified as it lives on"
385 " broken nodes", iname)
387 ToStdout("Instance %s has missing logical volumes:", iname)
389 for node, vol in ival:
391 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
393 ToStdout("\t%s /dev/xenvg/%s", node, vol)
394 ToStdout("You need to run replace_disks for all the above"
395 " instances, if this message persist after fixing nodes.")
402 def MasterFailover(opts, args):
403 """Failover the master node.
405 This command, when run on a non-master node, will cause the current
406 master to cease being master, and the non-master to become new
409 @param opts: the command line options selected by the user
411 @param args: should be an empty list
413 @return: the desired exit code
416 return bootstrap.MasterFailover()
419 def SearchTags(opts, args):
420 """Searches the tags on all the cluster.
422 @param opts: the command line options selected by the user
424 @param args: should contain only one element, the tag pattern
426 @return: the desired exit code
429 op = opcodes.OpSearchTags(pattern=args[0])
430 result = SubmitOpCode(op)
433 result = list(result)
435 for path, tag in result:
436 ToStdout("%s %s", path, tag)
439 def SetClusterParams(opts, args):
440 """Modify the cluster.
442 @param opts: the command line options selected by the user
444 @param args: should be an empty list
446 @return: the desired exit code
449 if not (not opts.lvm_storage or opts.vg_name or
450 opts.enabled_hypervisors or opts.hvparams or
452 ToStderr("Please give at least one of the parameters.")
455 vg_name = opts.vg_name
456 if not opts.lvm_storage and opts.vg_name:
457 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
460 hvlist = opts.enabled_hypervisors
461 if hvlist is not None:
462 hvlist = hvlist.split(",")
464 hvparams = opts.hvparams
466 # a list of (name, dict) we can pass directly to dict()
467 hvparams = dict(opts.hvparams)
469 beparams = opts.beparams
471 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
472 enabled_hypervisors=hvlist,
479 def QueueOps(opts, args):
482 @param opts: the command line options selected by the user
484 @param args: should contain only one element, the subcommand
486 @return: the desired exit code
491 if command in ("drain", "undrain"):
492 drain_flag = command == "drain"
493 client.SetQueueDrainFlag(drain_flag)
494 elif command == "info":
495 result = client.QueryConfigValues(["drain_flag"])
500 ToStdout("The drain flag is %s" % val)
503 # this is an option common to more than one command, so we declare
504 # it here and reuse it
505 node_option = make_option("-n", "--node", action="append", dest="nodes",
506 help="Node to copy to (if not given, all nodes),"
507 " can be given multiple times",
508 metavar="<node>", default=[])
511 'init': (InitCluster, ARGS_ONE,
513 make_option("-s", "--secondary-ip", dest="secondary_ip",
514 help="Specify the secondary ip for this node;"
515 " if given, the entire cluster must have secondary"
517 metavar="ADDRESS", default=None),
518 make_option("-m", "--mac-prefix", dest="mac_prefix",
519 help="Specify the mac prefix for the instance IP"
520 " addresses, in the format XX:XX:XX",
522 default="aa:00:00",),
523 make_option("-g", "--vg-name", dest="vg_name",
524 help="Specify the volume group name "
525 " (cluster-wide) for disk allocation [xenvg]",
528 make_option("-b", "--bridge", dest="def_bridge",
529 help="Specify the default bridge name (cluster-wide)"
530 " to connect the instances to [%s]" %
531 constants.DEFAULT_BRIDGE,
533 default=constants.DEFAULT_BRIDGE,),
534 make_option("--master-netdev", dest="master_netdev",
535 help="Specify the node interface (cluster-wide)"
536 " on which the master IP address will be added "
537 " [%s]" % constants.DEFAULT_BRIDGE,
539 default=constants.DEFAULT_BRIDGE,),
540 make_option("--file-storage-dir", dest="file_storage_dir",
541 help="Specify the default directory (cluster-wide)"
542 " for storing the file-based disks [%s]" %
543 constants.DEFAULT_FILE_STORAGE_DIR,
545 default=constants.DEFAULT_FILE_STORAGE_DIR,),
546 make_option("--no-lvm-storage", dest="lvm_storage",
547 help="No support for lvm based instances"
549 action="store_false", default=True,),
550 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
551 help="Comma-separated list of hypervisors",
552 type="string", default=None),
553 make_option("-t", "--default-hypervisor",
554 dest="default_hypervisor",
555 help="Default hypervisor to use for instance creation",
556 choices=list(constants.HYPER_TYPES),
557 default=constants.DEFAULT_ENABLED_HYPERVISOR),
558 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
559 help="Hypervisor and hypervisor options, in the"
561 " hypervisor:option=value,option=value,...",
565 keyval_option("-B", "--backend-parameters", dest="beparams",
566 type="keyval", default={},
567 help="Backend parameters"),
569 "[opts...] <cluster_name>",
570 "Initialises a new cluster configuration"),
571 'destroy': (DestroyCluster, ARGS_NONE,
573 make_option("--yes-do-it", dest="yes_do_it",
574 help="Destroy cluster",
575 action="store_true"),
577 "", "Destroy cluster"),
578 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
580 "Renames the cluster"),
581 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
582 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
583 help="Skip N+1 memory redundancy tests",
587 "", "Does a check on the cluster configuration"),
588 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
589 "", "Does a check on the cluster disk status"),
590 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
591 "", "Makes the current node the master"),
592 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
593 "", "Shows the cluster version"),
594 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
595 "", "Shows the cluster master"),
596 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
597 "[-n node...] <filename>",
598 "Copies a file to all (or only some) nodes"),
599 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
600 "[-n node...] <command>",
601 "Runs a command on all (or only some) nodes"),
602 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
603 "", "Show cluster configuration"),
604 'list-tags': (ListTags, ARGS_NONE,
605 [DEBUG_OPT], "", "List the tags of the cluster"),
606 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
607 "tag...", "Add tags to the cluster"),
608 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
609 "tag...", "Remove tags from the cluster"),
610 'search-tags': (SearchTags, ARGS_ONE,
611 [DEBUG_OPT], "", "Searches the tags on all objects on"
612 " the cluster for a given pattern (regex)"),
613 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
614 "drain|undrain|info", "Change queue properties"),
615 'modify': (SetClusterParams, ARGS_NONE,
617 make_option("-g", "--vg-name", dest="vg_name",
618 help="Specify the volume group name "
619 " (cluster-wide) for disk allocation "
620 "and enable lvm based storage",
622 make_option("--no-lvm-storage", dest="lvm_storage",
623 help="Disable support for lvm based instances"
625 action="store_false", default=True,),
626 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
627 help="Comma-separated list of hypervisors",
628 type="string", default=None),
629 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
630 help="Hypervisor and hypervisor options, in the"
632 " hypervisor:option=value,option=value,...",
636 keyval_option("-B", "--backend-parameters", dest="beparams",
637 type="keyval", default={},
638 help="Backend parameters"),
641 "Alters the parameters of the cluster"),
644 if __name__ == '__main__':
645 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))