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
23 from optparse import make_option
27 from ganeti.cli import *
28 from ganeti import opcodes
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import utils
32 from ganeti import bootstrap
33 from ganeti import ssh
34 from ganeti import ssconf
37 def InitCluster(opts, args):
38 """Initialize the cluster.
41 opts - class with options as members
42 args - list of arguments, expected to be [clustername]
45 if not opts.lvm_storage and opts.vg_name:
46 print ("Options --no-lvm-storage and --vg-name conflict.")
49 vg_name = opts.vg_name
50 if opts.lvm_storage and not opts.vg_name:
51 vg_name = constants.DEFAULT_VG
53 hvlist = opts.enabled_hypervisors
54 if hvlist is not None:
55 hvlist = hvlist.split(",")
57 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
59 hvparams = opts.hvparams
61 # a list of (name, dict) we can pass directly to dict()
62 hvparams = dict(opts.hvparams)
64 # otherwise init as empty dict
67 beparams = opts.beparams
68 # check for invalid parameters
69 for parameter in beparams:
70 if parameter not in constants.BES_PARAMETERS:
71 print "Invalid backend parameter: %s" % parameter
74 # prepare beparams dict
75 for parameter in constants.BES_PARAMETERS:
76 if parameter not in beparams:
77 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
81 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
83 print "%s must be an integer" % constants.BE_VCPUS
86 beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
88 # prepare hvparams dict
89 for hv in constants.HYPER_TYPES:
90 if hv not in hvparams:
92 for parameter in constants.HVC_DEFAULTS[hv]:
93 if parameter not in hvparams[hv]:
94 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
97 if hv not in constants.HYPER_TYPES:
98 print "invalid hypervisor: %s" % hv
101 bootstrap.InitCluster(cluster_name=args[0],
102 secondary_ip=opts.secondary_ip,
103 hypervisor_type=opts.hypervisor_type,
105 mac_prefix=opts.mac_prefix,
106 def_bridge=opts.def_bridge,
107 master_netdev=opts.master_netdev,
108 file_storage_dir=opts.file_storage_dir,
109 enabled_hypervisors=hvlist,
115 def DestroyCluster(opts, args):
116 """Destroy the cluster.
119 opts - class with options as members
122 if not opts.yes_do_it:
123 print ("Destroying a cluster is irreversibly. If you really want destroy"
124 " this cluster, supply the --yes-do-it option.")
127 op = opcodes.OpDestroyCluster()
128 master = SubmitOpCode(op)
129 # if we reached this, the opcode didn't fail; we can proceed to
130 # shutdown all the daemons
131 bootstrap.FinalizeClusterDestroy(master)
135 def RenameCluster(opts, args):
136 """Rename the cluster.
139 opts - class with options as members, we use force only
140 args - list of arguments, expected to be [new_name]
145 usertext = ("This will rename the cluster to '%s'. If you are connected"
146 " over the network to the cluster name, the operation is very"
147 " dangerous as the IP address will be removed from the node"
148 " and the change may not go through. Continue?") % name
149 if not AskUser(usertext):
152 op = opcodes.OpRenameCluster(name=name)
157 def ShowClusterVersion(opts, args):
158 """Write version of ganeti software to the standard output.
161 opts - class with options as members
164 op = opcodes.OpQueryClusterInfo()
165 result = SubmitOpCode(op)
166 print ("Software version: %s" % result["software_version"])
167 print ("Internode protocol: %s" % result["protocol_version"])
168 print ("Configuration format: %s" % result["config_version"])
169 print ("OS api version: %s" % result["os_api_version"])
170 print ("Export interface: %s" % result["export_version"])
174 def ShowClusterMaster(opts, args):
175 """Write name of master node to the standard output.
178 opts - class with options as members
181 print GetClient().QueryConfigValues(["master_node"])[0]
185 def ShowClusterConfig(opts, args):
186 """Shows cluster information.
189 op = opcodes.OpQueryClusterInfo()
190 result = SubmitOpCode(op)
192 print ("Cluster name: %s" % result["name"])
194 print ("Master node: %s" % result["master"])
196 print ("Architecture (this node): %s (%s)" %
197 (result["architecture"][0], result["architecture"][1]))
199 print ("Default hypervisor: %s" % result["hypervisor_type"])
200 print ("Enabled hypervisors: %s" % ", ".join(result["enabled_hypervisors"]))
202 print "Hypervisor parameters:"
203 for hv_name, hv_dict in result["hvparams"].items():
204 print " - %s:" % hv_name
205 for item, val in hv_dict.iteritems():
206 print " %s: %s" % (item, val)
208 print "Cluster parameters:"
209 for gr_name, gr_dict in result["beparams"].items():
210 print " - %s:" % gr_name
211 for item, val in gr_dict.iteritems():
212 print " %s: %s" % (item, val)
217 def ClusterCopyFile(opts, args):
218 """Copy a file from master to some nodes.
221 opts - class with options as members
222 args - list containing a single element, the file name
224 nodes - list containing the name of target nodes; if empty, all nodes
228 if not os.path.exists(filename):
229 raise errors.OpPrereqError("No such filename '%s'" % filename)
233 myname = utils.HostInfo().name
235 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
237 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
238 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
240 srun = ssh.SshRunner(cluster_name=cluster_name)
242 if not srun.CopyFileToNode(node, filename):
243 print >> sys.stderr, ("Copy of file %s to node %s failed" %
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 print ("------------------------------------------------")
278 print ("node: %s" % name)
279 print ("%s" % result.output)
280 print ("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 print "Nodes unreachable or with bad data:"
320 retcode = constants.EXIT_SUCCESS
323 for node, text in nlvm.iteritems():
324 print ("Error on node %s: LVM error: %s" %
325 (node, text[-400:].encode('string_escape')))
327 print "You need to fix these nodes first before fixing instances"
330 for iname in instances:
333 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
335 print "Activating disks for instance '%s'" % iname
337 except errors.GenericError, err:
338 nret, msg = FormatError(err)
340 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
344 for iname, ival in missing.iteritems():
345 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
347 print ("Instance %s cannot be verified as it lives on"
348 " broken nodes" % iname)
350 print "Instance %s has missing logical volumes:" % iname
352 for node, vol in ival:
354 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
356 print ("\t%s /dev/xenvg/%s" % (node, vol))
357 print ("You need to run replace_disks for all the above"
358 " instances, if this message persist after fixing nodes.")
364 def MasterFailover(opts, args):
365 """Failover the master node.
367 This command, when run on a non-master node, will cause the current
368 master to cease being master, and the non-master to become new
372 return bootstrap.MasterFailover()
375 def SearchTags(opts, args):
376 """Searches the tags on all the cluster.
379 op = opcodes.OpSearchTags(pattern=args[0])
380 result = SubmitOpCode(op)
383 result = list(result)
385 for path, tag in result:
386 print "%s %s" % (path, tag)
389 def SetClusterParams(opts, args):
390 """Modify the cluster.
393 opts - class with options as members
396 if not (not opts.lvm_storage or opts.vg_name or
397 opts.enabled_hypervisors or opts.hvparams or
399 print "Please give at least one of the parameters."
402 vg_name = opts.vg_name
403 if not opts.lvm_storage and opts.vg_name:
404 print ("Options --no-lvm-storage and --vg-name conflict.")
407 hvlist = opts.enabled_hypervisors
408 if hvlist is not None:
409 hvlist = hvlist.split(",")
411 hvparams = opts.hvparams
413 # a list of (name, dict) we can pass directly to dict()
414 hvparams = dict(opts.hvparams)
416 beparams = opts.beparams
418 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
419 enabled_hypervisors=hvlist,
426 def QueueOps(opts, args):
432 if command in ("drain", "undrain"):
433 drain_flag = command == "drain"
434 client.SetQueueDrainFlag(drain_flag)
435 elif command == "info":
436 result = client.QueryConfigValues(["drain_flag"])
437 print "The drain flag is",
444 # this is an option common to more than one command, so we declare
445 # it here and reuse it
446 node_option = make_option("-n", "--node", action="append", dest="nodes",
447 help="Node to copy to (if not given, all nodes),"
448 " can be given multiple times",
449 metavar="<node>", default=[])
452 'init': (InitCluster, ARGS_ONE,
454 make_option("-s", "--secondary-ip", dest="secondary_ip",
455 help="Specify the secondary ip for this node;"
456 " if given, the entire cluster must have secondary"
458 metavar="ADDRESS", default=None),
459 make_option("-t", "--hypervisor-type", dest="hypervisor_type",
460 help="Specify the hypervisor type "
461 "(xen-pvm, kvm, fake, xen-hvm)",
462 metavar="TYPE", choices=["xen-pvm",
467 make_option("-m", "--mac-prefix", dest="mac_prefix",
468 help="Specify the mac prefix for the instance IP"
469 " addresses, in the format XX:XX:XX",
471 default="aa:00:00",),
472 make_option("-g", "--vg-name", dest="vg_name",
473 help="Specify the volume group name "
474 " (cluster-wide) for disk allocation [xenvg]",
477 make_option("-b", "--bridge", dest="def_bridge",
478 help="Specify the default bridge name (cluster-wide)"
479 " to connect the instances to [%s]" %
480 constants.DEFAULT_BRIDGE,
482 default=constants.DEFAULT_BRIDGE,),
483 make_option("--master-netdev", dest="master_netdev",
484 help="Specify the node interface (cluster-wide)"
485 " on which the master IP address will be added "
486 " [%s]" % constants.DEFAULT_BRIDGE,
488 default=constants.DEFAULT_BRIDGE,),
489 make_option("--file-storage-dir", dest="file_storage_dir",
490 help="Specify the default directory (cluster-wide)"
491 " for storing the file-based disks [%s]" %
492 constants.DEFAULT_FILE_STORAGE_DIR,
494 default=constants.DEFAULT_FILE_STORAGE_DIR,),
495 make_option("--no-lvm-storage", dest="lvm_storage",
496 help="No support for lvm based instances"
498 action="store_false", default=True,),
499 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
500 help="Comma-separated list of hypervisors",
501 type="string", default=None),
502 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
503 help="Hypervisor and hypervisor options, in the"
505 " hypervisor:option=value,option=value,...",
509 keyval_option("-B", "--backend-parameters", dest="beparams",
510 type="keyval", default={},
511 help="Backend parameters"),
513 "[opts...] <cluster_name>",
514 "Initialises a new cluster configuration"),
515 'destroy': (DestroyCluster, ARGS_NONE,
517 make_option("--yes-do-it", dest="yes_do_it",
518 help="Destroy cluster",
519 action="store_true"),
521 "", "Destroy cluster"),
522 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
524 "Renames the cluster"),
525 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
526 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
527 help="Skip N+1 memory redundancy tests",
531 "", "Does a check on the cluster configuration"),
532 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
533 "", "Does a check on the cluster disk status"),
534 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
535 "", "Makes the current node the master"),
536 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
537 "", "Shows the cluster version"),
538 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
539 "", "Shows the cluster master"),
540 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
541 "[-n node...] <filename>",
542 "Copies a file to all (or only some) nodes"),
543 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
544 "[-n node...] <command>",
545 "Runs a command on all (or only some) nodes"),
546 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
547 "", "Show cluster configuration"),
548 'list-tags': (ListTags, ARGS_NONE,
549 [DEBUG_OPT], "", "List the tags of the cluster"),
550 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
551 "tag...", "Add tags to the cluster"),
552 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
553 "tag...", "Remove tags from the cluster"),
554 'search-tags': (SearchTags, ARGS_ONE,
555 [DEBUG_OPT], "", "Searches the tags on all objects on"
556 " the cluster for a given pattern (regex)"),
557 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
558 "drain|undrain|info", "Change queue properties"),
559 'modify': (SetClusterParams, ARGS_NONE,
561 make_option("-g", "--vg-name", dest="vg_name",
562 help="Specify the volume group name "
563 " (cluster-wide) for disk allocation "
564 "and enable lvm based storage",
566 make_option("--no-lvm-storage", dest="lvm_storage",
567 help="Disable support for lvm based instances"
569 action="store_false", default=True,),
570 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
571 help="Comma-separated list of hypervisors",
572 type="string", default=None),
573 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
574 help="Hypervisor and hypervisor options, in the"
576 " hypervisor:option=value,option=value,...",
580 keyval_option("-B", "--backend-parameters", dest="beparams",
581 type="keyval", default={},
582 help="Backend parameters"),
585 "Alters the parameters of the cluster"),
588 if __name__ == '__main__':
589 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))