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,
104 mac_prefix=opts.mac_prefix,
105 def_bridge=opts.def_bridge,
106 master_netdev=opts.master_netdev,
107 file_storage_dir=opts.file_storage_dir,
108 enabled_hypervisors=hvlist,
114 def DestroyCluster(opts, args):
115 """Destroy the cluster.
118 opts - class with options as members
121 if not opts.yes_do_it:
122 print ("Destroying a cluster is irreversibly. If you really want destroy"
123 " this cluster, supply the --yes-do-it option.")
126 op = opcodes.OpDestroyCluster()
127 master = SubmitOpCode(op)
128 # if we reached this, the opcode didn't fail; we can proceed to
129 # shutdown all the daemons
130 bootstrap.FinalizeClusterDestroy(master)
134 def RenameCluster(opts, args):
135 """Rename the cluster.
138 opts - class with options as members, we use force only
139 args - list of arguments, expected to be [new_name]
144 usertext = ("This will rename the cluster to '%s'. If you are connected"
145 " over the network to the cluster name, the operation is very"
146 " dangerous as the IP address will be removed from the node"
147 " and the change may not go through. Continue?") % name
148 if not AskUser(usertext):
151 op = opcodes.OpRenameCluster(name=name)
156 def ShowClusterVersion(opts, args):
157 """Write version of ganeti software to the standard output.
160 opts - class with options as members
163 op = opcodes.OpQueryClusterInfo()
164 result = SubmitOpCode(op)
165 print ("Software version: %s" % result["software_version"])
166 print ("Internode protocol: %s" % result["protocol_version"])
167 print ("Configuration format: %s" % result["config_version"])
168 print ("OS api version: %s" % result["os_api_version"])
169 print ("Export interface: %s" % result["export_version"])
173 def ShowClusterMaster(opts, args):
174 """Write name of master node to the standard output.
177 opts - class with options as members
180 print GetClient().QueryConfigValues(["master_node"])[0]
184 def ShowClusterConfig(opts, args):
185 """Shows cluster information.
188 op = opcodes.OpQueryClusterInfo()
189 result = SubmitOpCode(op)
191 print ("Cluster name: %s" % result["name"])
193 print ("Master node: %s" % result["master"])
195 print ("Architecture (this node): %s (%s)" %
196 (result["architecture"][0], result["architecture"][1]))
198 print ("Default hypervisor: %s" % result["hypervisor_type"])
199 print ("Enabled hypervisors: %s" % ", ".join(result["enabled_hypervisors"]))
201 print "Hypervisor parameters:"
202 for hv_name, hv_dict in result["hvparams"].items():
203 print " - %s:" % hv_name
204 for item, val in hv_dict.iteritems():
205 print " %s: %s" % (item, val)
207 print "Cluster parameters:"
208 for gr_name, gr_dict in result["beparams"].items():
209 print " - %s:" % gr_name
210 for item, val in gr_dict.iteritems():
211 print " %s: %s" % (item, val)
216 def ClusterCopyFile(opts, args):
217 """Copy a file from master to some nodes.
220 opts - class with options as members
221 args - list containing a single element, the file name
223 nodes - list containing the name of target nodes; if empty, all nodes
227 if not os.path.exists(filename):
228 raise errors.OpPrereqError("No such filename '%s'" % filename)
232 myname = utils.HostInfo().name
234 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
236 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
237 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
239 srun = ssh.SshRunner(cluster_name=cluster_name)
241 if not srun.CopyFileToNode(node, filename):
242 print >> sys.stderr, ("Copy of file %s to node %s failed" %
248 def RunClusterCommand(opts, args):
249 """Run a command on some nodes.
252 opts - class with options as members
253 args - the command list as a list
255 nodes: list containing the name of target nodes; if empty, all nodes
260 command = " ".join(args)
261 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
262 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
264 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
267 srun = ssh.SshRunner(cluster_name=cluster_name)
269 # Make sure master node is at list end
270 if master_node in nodes:
271 nodes.remove(master_node)
272 nodes.append(master_node)
275 result = srun.Run(name, "root", command)
276 print ("------------------------------------------------")
277 print ("node: %s" % name)
278 print ("%s" % result.output)
279 print ("return code = %s" % result.exit_code)
284 def VerifyCluster(opts, args):
285 """Verify integrity of cluster, performing various test on nodes.
288 opts - class with options as members
292 if opts.skip_nplusone_mem:
293 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
294 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
301 def VerifyDisks(opts, args):
302 """Verify integrity of cluster disks.
305 opts - class with options as members
308 op = opcodes.OpVerifyDisks()
309 result = SubmitOpCode(op)
310 if not isinstance(result, (list, tuple)) or len(result) != 4:
311 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
313 nodes, nlvm, instances, missing = result
316 print "Nodes unreachable or with bad data:"
319 retcode = constants.EXIT_SUCCESS
322 for node, text in nlvm.iteritems():
323 print ("Error on node %s: LVM error: %s" %
324 (node, text[-400:].encode('string_escape')))
326 print "You need to fix these nodes first before fixing instances"
329 for iname in instances:
332 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
334 print "Activating disks for instance '%s'" % iname
336 except errors.GenericError, err:
337 nret, msg = FormatError(err)
339 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
343 for iname, ival in missing.iteritems():
344 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
346 print ("Instance %s cannot be verified as it lives on"
347 " broken nodes" % iname)
349 print "Instance %s has missing logical volumes:" % iname
351 for node, vol in ival:
353 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
355 print ("\t%s /dev/xenvg/%s" % (node, vol))
356 print ("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 print "%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 print "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 print ("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"])
436 print "The drain flag is",
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}))