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.
43 opts - class with options as members
44 args - list of arguments, expected to be [clustername]
47 if not opts.lvm_storage and opts.vg_name:
48 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
51 vg_name = opts.vg_name
52 if opts.lvm_storage and not opts.vg_name:
53 vg_name = constants.DEFAULT_VG
55 hvlist = opts.enabled_hypervisors
56 if hvlist is not None:
57 hvlist = hvlist.split(",")
59 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
61 hvparams = opts.hvparams
63 # a list of (name, dict) we can pass directly to dict()
64 hvparams = dict(opts.hvparams)
66 # otherwise init as empty dict
69 beparams = opts.beparams
70 # check for invalid parameters
71 for parameter in beparams:
72 if parameter not in constants.BES_PARAMETERS:
73 print "Invalid backend parameter: %s" % parameter
76 # prepare beparams dict
77 for parameter in constants.BES_PARAMETERS:
78 if parameter not in beparams:
79 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
83 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
85 print "%s must be an integer" % constants.BE_VCPUS
88 beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
90 # prepare hvparams dict
91 for hv in constants.HYPER_TYPES:
92 if hv not in hvparams:
94 for parameter in constants.HVC_DEFAULTS[hv]:
95 if parameter not in hvparams[hv]:
96 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
99 if hv not in constants.HYPER_TYPES:
100 print "invalid hypervisor: %s" % hv
103 bootstrap.InitCluster(cluster_name=args[0],
104 secondary_ip=opts.secondary_ip,
106 mac_prefix=opts.mac_prefix,
107 def_bridge=opts.def_bridge,
108 master_netdev=opts.master_netdev,
109 file_storage_dir=opts.file_storage_dir,
110 enabled_hypervisors=hvlist,
116 def DestroyCluster(opts, args):
117 """Destroy the cluster.
120 opts - class with options as members
123 if not opts.yes_do_it:
124 ToStderr("Destroying a cluster is irreversible. If you really want"
125 " destroy this cluster, supply the --yes-do-it option.")
128 op = opcodes.OpDestroyCluster()
129 master = SubmitOpCode(op)
130 # if we reached this, the opcode didn't fail; we can proceed to
131 # shutdown all the daemons
132 bootstrap.FinalizeClusterDestroy(master)
136 def RenameCluster(opts, args):
137 """Rename the cluster.
140 opts - class with options as members, we use force only
141 args - list of arguments, expected to be [new_name]
146 usertext = ("This will rename the cluster to '%s'. If you are connected"
147 " over the network to the cluster name, the operation is very"
148 " dangerous as the IP address will be removed from the node"
149 " and the change may not go through. Continue?") % name
150 if not AskUser(usertext):
153 op = opcodes.OpRenameCluster(name=name)
158 def ShowClusterVersion(opts, args):
159 """Write version of ganeti software to the standard output.
162 opts - class with options as members
165 op = opcodes.OpQueryClusterInfo()
166 result = SubmitOpCode(op)
167 ToStdout("Software version: %s", result["software_version"])
168 ToStdout("Internode protocol: %s", result["protocol_version"])
169 ToStdout("Configuration format: %s", result["config_version"])
170 ToStdout("OS api version: %s", result["os_api_version"])
171 ToStdout("Export interface: %s", result["export_version"])
175 def ShowClusterMaster(opts, args):
176 """Write name of master node to the standard output.
179 opts - class with options as members
182 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
186 def ShowClusterConfig(opts, args):
187 """Shows cluster information.
190 op = opcodes.OpQueryClusterInfo()
191 result = SubmitOpCode(op)
193 ToStdout("Cluster name: %s", result["name"])
195 ToStdout("Master node: %s", result["master"])
197 ToStdout("Architecture (this node): %s (%s)",
198 result["architecture"][0], result["architecture"][1])
200 ToStdout("Default hypervisor: %s", result["hypervisor_type"])
201 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
203 ToStdout("Hypervisor parameters:")
204 for hv_name, hv_dict in result["hvparams"].items():
205 ToStdout(" - %s:", hv_name)
206 for item, val in hv_dict.iteritems():
207 ToStdout(" %s: %s", item, val)
209 ToStdout("Cluster parameters:")
210 for gr_name, gr_dict in result["beparams"].items():
211 ToStdout(" - %s:", gr_name)
212 for item, val in gr_dict.iteritems():
213 ToStdout(" %s: %s", item, val)
218 def ClusterCopyFile(opts, args):
219 """Copy a file from master to some nodes.
222 opts - class with options as members
223 args - list containing a single element, the file name
225 nodes - list containing the name of target nodes; if empty, all nodes
229 if not os.path.exists(filename):
230 raise errors.OpPrereqError("No such filename '%s'" % filename)
234 myname = utils.HostInfo().name
236 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
238 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
239 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
241 srun = ssh.SshRunner(cluster_name=cluster_name)
243 if not srun.CopyFileToNode(node, filename):
244 ToStderr("Copy of file %s to node %s failed", filename, node)
249 def RunClusterCommand(opts, args):
250 """Run a command on some nodes.
253 opts - class with options as members
254 args - the command list as a list
256 nodes: list containing the name of target nodes; if empty, all nodes
261 command = " ".join(args)
262 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
263 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
265 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
268 srun = ssh.SshRunner(cluster_name=cluster_name)
270 # Make sure master node is at list end
271 if master_node in nodes:
272 nodes.remove(master_node)
273 nodes.append(master_node)
276 result = srun.Run(name, "root", command)
277 ToStdout("------------------------------------------------")
278 ToStdout("node: %s", name)
279 ToStdout("%s", result.output)
280 ToStdout("return code = %s", result.exit_code)
285 def VerifyCluster(opts, args):
286 """Verify integrity of cluster, performing various test on nodes.
289 opts - class with options as members
293 if opts.skip_nplusone_mem:
294 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
295 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
302 def VerifyDisks(opts, args):
303 """Verify integrity of cluster disks.
306 opts - class with options as members
309 op = opcodes.OpVerifyDisks()
310 result = SubmitOpCode(op)
311 if not isinstance(result, (list, tuple)) or len(result) != 4:
312 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
314 nodes, nlvm, instances, missing = result
317 ToStdout("Nodes unreachable or with bad data:")
319 ToStdout("\t%s", name)
320 retcode = constants.EXIT_SUCCESS
323 for node, text in nlvm.iteritems():
324 ToStdout("Error on node %s: LVM error: %s",
325 node, text[-400:].encode('string_escape'))
327 ToStdout("You need to fix these nodes first before fixing instances")
330 for iname in instances:
333 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
335 ToStdout("Activating disks for instance '%s'", iname)
337 except errors.GenericError, err:
338 nret, msg = FormatError(err)
340 ToStderr("Error activating disks for instance %s: %s", iname, msg)
343 for iname, ival in missing.iteritems():
344 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
346 ToStdout("Instance %s cannot be verified as it lives on"
347 " broken nodes", iname)
349 ToStdout("Instance %s has missing logical volumes:", iname)
351 for node, vol in ival:
353 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
355 ToStdout("\t%s /dev/xenvg/%s", node, vol)
356 ToStdout("You need to run replace_disks for all the above"
357 " instances, if this message persist after fixing nodes.")
363 def MasterFailover(opts, args):
364 """Failover the master node.
366 This command, when run on a non-master node, will cause the current
367 master to cease being master, and the non-master to become new
371 return bootstrap.MasterFailover()
374 def SearchTags(opts, args):
375 """Searches the tags on all the cluster.
378 op = opcodes.OpSearchTags(pattern=args[0])
379 result = SubmitOpCode(op)
382 result = list(result)
384 for path, tag in result:
385 ToStdout("%s %s", path, tag)
388 def SetClusterParams(opts, args):
389 """Modify the cluster.
392 opts - class with options as members
395 if not (not opts.lvm_storage or opts.vg_name or
396 opts.enabled_hypervisors or opts.hvparams or
398 ToStderr("Please give at least one of the parameters.")
401 vg_name = opts.vg_name
402 if not opts.lvm_storage and opts.vg_name:
403 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
406 hvlist = opts.enabled_hypervisors
407 if hvlist is not None:
408 hvlist = hvlist.split(",")
410 hvparams = opts.hvparams
412 # a list of (name, dict) we can pass directly to dict()
413 hvparams = dict(opts.hvparams)
415 beparams = opts.beparams
417 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
418 enabled_hypervisors=hvlist,
425 def QueueOps(opts, args):
431 if command in ("drain", "undrain"):
432 drain_flag = command == "drain"
433 client.SetQueueDrainFlag(drain_flag)
434 elif command == "info":
435 result = client.QueryConfigValues(["drain_flag"])
440 ToStdout("The drain flag is %s" % val)
443 # this is an option common to more than one command, so we declare
444 # it here and reuse it
445 node_option = make_option("-n", "--node", action="append", dest="nodes",
446 help="Node to copy to (if not given, all nodes),"
447 " can be given multiple times",
448 metavar="<node>", default=[])
451 'init': (InitCluster, ARGS_ONE,
453 make_option("-s", "--secondary-ip", dest="secondary_ip",
454 help="Specify the secondary ip for this node;"
455 " if given, the entire cluster must have secondary"
457 metavar="ADDRESS", default=None),
458 make_option("-m", "--mac-prefix", dest="mac_prefix",
459 help="Specify the mac prefix for the instance IP"
460 " addresses, in the format XX:XX:XX",
462 default="aa:00:00",),
463 make_option("-g", "--vg-name", dest="vg_name",
464 help="Specify the volume group name "
465 " (cluster-wide) for disk allocation [xenvg]",
468 make_option("-b", "--bridge", dest="def_bridge",
469 help="Specify the default bridge name (cluster-wide)"
470 " to connect the instances to [%s]" %
471 constants.DEFAULT_BRIDGE,
473 default=constants.DEFAULT_BRIDGE,),
474 make_option("--master-netdev", dest="master_netdev",
475 help="Specify the node interface (cluster-wide)"
476 " on which the master IP address will be added "
477 " [%s]" % constants.DEFAULT_BRIDGE,
479 default=constants.DEFAULT_BRIDGE,),
480 make_option("--file-storage-dir", dest="file_storage_dir",
481 help="Specify the default directory (cluster-wide)"
482 " for storing the file-based disks [%s]" %
483 constants.DEFAULT_FILE_STORAGE_DIR,
485 default=constants.DEFAULT_FILE_STORAGE_DIR,),
486 make_option("--no-lvm-storage", dest="lvm_storage",
487 help="No support for lvm based instances"
489 action="store_false", default=True,),
490 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
491 help="Comma-separated list of hypervisors",
492 type="string", default=None),
493 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
494 help="Hypervisor and hypervisor options, in the"
496 " hypervisor:option=value,option=value,...",
500 keyval_option("-B", "--backend-parameters", dest="beparams",
501 type="keyval", default={},
502 help="Backend parameters"),
504 "[opts...] <cluster_name>",
505 "Initialises a new cluster configuration"),
506 'destroy': (DestroyCluster, ARGS_NONE,
508 make_option("--yes-do-it", dest="yes_do_it",
509 help="Destroy cluster",
510 action="store_true"),
512 "", "Destroy cluster"),
513 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
515 "Renames the cluster"),
516 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
517 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
518 help="Skip N+1 memory redundancy tests",
522 "", "Does a check on the cluster configuration"),
523 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
524 "", "Does a check on the cluster disk status"),
525 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
526 "", "Makes the current node the master"),
527 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
528 "", "Shows the cluster version"),
529 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
530 "", "Shows the cluster master"),
531 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
532 "[-n node...] <filename>",
533 "Copies a file to all (or only some) nodes"),
534 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
535 "[-n node...] <command>",
536 "Runs a command on all (or only some) nodes"),
537 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
538 "", "Show cluster configuration"),
539 'list-tags': (ListTags, ARGS_NONE,
540 [DEBUG_OPT], "", "List the tags of the cluster"),
541 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
542 "tag...", "Add tags to the cluster"),
543 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
544 "tag...", "Remove tags from the cluster"),
545 'search-tags': (SearchTags, ARGS_ONE,
546 [DEBUG_OPT], "", "Searches the tags on all objects on"
547 " the cluster for a given pattern (regex)"),
548 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
549 "drain|undrain|info", "Change queue properties"),
550 'modify': (SetClusterParams, ARGS_NONE,
552 make_option("-g", "--vg-name", dest="vg_name",
553 help="Specify the volume group name "
554 " (cluster-wide) for disk allocation "
555 "and enable lvm based storage",
557 make_option("--no-lvm-storage", dest="lvm_storage",
558 help="Disable support for lvm based instances"
560 action="store_false", default=True,),
561 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
562 help="Comma-separated list of hypervisors",
563 type="string", default=None),
564 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
565 help="Hypervisor and hypervisor options, in the"
567 " hypervisor:option=value,option=value,...",
571 keyval_option("-B", "--backend-parameters", dest="beparams",
572 type="keyval", default={},
573 help="Backend parameters"),
576 "Alters the parameters of the cluster"),
579 if __name__ == '__main__':
580 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))