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 bootstrap.InitCluster(cluster_name=args[0],
54 secondary_ip=opts.secondary_ip,
55 hypervisor_type=opts.hypervisor_type,
57 mac_prefix=opts.mac_prefix,
58 def_bridge=opts.def_bridge,
59 master_netdev=opts.master_netdev,
60 file_storage_dir=opts.file_storage_dir)
64 def DestroyCluster(opts, args):
65 """Destroy the cluster.
68 opts - class with options as members
71 if not opts.yes_do_it:
72 print ("Destroying a cluster is irreversibly. If you really want destroy"
73 " this cluster, supply the --yes-do-it option.")
76 op = opcodes.OpDestroyCluster()
77 master = SubmitOpCode(op)
78 # if we reached this, the opcode didn't fail; we can proceed to
79 # shutdown all the daemons
80 bootstrap.FinalizeClusterDestroy(master)
84 def RenameCluster(opts, args):
85 """Rename the cluster.
88 opts - class with options as members, we use force only
89 args - list of arguments, expected to be [new_name]
94 usertext = ("This will rename the cluster to '%s'. If you are connected"
95 " over the network to the cluster name, the operation is very"
96 " dangerous as the IP address will be removed from the node"
97 " and the change may not go through. Continue?") % name
98 if not AskUser(usertext):
101 op = opcodes.OpRenameCluster(name=name)
106 def ShowClusterVersion(opts, args):
107 """Write version of ganeti software to the standard output.
110 opts - class with options as members
113 op = opcodes.OpQueryClusterInfo()
114 result = SubmitOpCode(op)
115 print ("Software version: %s" % result["software_version"])
116 print ("Internode protocol: %s" % result["protocol_version"])
117 print ("Configuration format: %s" % result["config_version"])
118 print ("OS api version: %s" % result["os_api_version"])
119 print ("Export interface: %s" % result["export_version"])
123 def ShowClusterMaster(opts, args):
124 """Write name of master node to the standard output.
127 opts - class with options as members
130 print GetClient().QueryConfigValues(["master_node"])[0]
134 def ShowClusterConfig(opts, args):
135 """Shows cluster information.
138 op = opcodes.OpQueryClusterInfo()
139 result = SubmitOpCode(op)
141 print ("Cluster name: %s" % result["name"])
143 print ("Master node: %s" % result["master"])
145 print ("Architecture (this node): %s (%s)" %
146 (result["architecture"][0], result["architecture"][1]))
148 print ("Cluster hypervisor: %s" % result["hypervisor_type"])
153 def ClusterCopyFile(opts, args):
154 """Copy a file from master to some nodes.
157 opts - class with options as members
158 args - list containing a single element, the file name
160 nodes - list containing the name of target nodes; if empty, all nodes
164 if not os.path.exists(filename):
165 raise errors.OpPrereqError("No such filename '%s'" % filename)
169 myname = utils.HostInfo().name
171 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
173 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
174 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
176 srun = ssh.SshRunner(cluster_name=cluster_name)
178 if not srun.CopyFileToNode(node, filename):
179 print >> sys.stderr, ("Copy of file %s to node %s failed" %
185 def RunClusterCommand(opts, args):
186 """Run a command on some nodes.
189 opts - class with options as members
190 args - the command list as a list
192 nodes: list containing the name of target nodes; if empty, all nodes
197 command = " ".join(args)
198 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
199 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
201 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
204 srun = ssh.SshRunner(cluster_name=cluster_name)
206 # Make sure master node is at list end
207 if master_node in nodes:
208 nodes.remove(master_node)
209 nodes.append(master_node)
212 result = srun.Run(name, "root", command)
213 print ("------------------------------------------------")
214 print ("node: %s" % name)
215 print ("%s" % result.output)
216 print ("return code = %s" % result.exit_code)
221 def VerifyCluster(opts, args):
222 """Verify integrity of cluster, performing various test on nodes.
225 opts - class with options as members
229 if opts.skip_nplusone_mem:
230 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
231 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
238 def VerifyDisks(opts, args):
239 """Verify integrity of cluster disks.
242 opts - class with options as members
245 op = opcodes.OpVerifyDisks()
246 result = SubmitOpCode(op)
247 if not isinstance(result, (list, tuple)) or len(result) != 4:
248 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
250 nodes, nlvm, instances, missing = result
253 print "Nodes unreachable or with bad data:"
256 retcode = constants.EXIT_SUCCESS
259 for node, text in nlvm.iteritems():
260 print ("Error on node %s: LVM error: %s" %
261 (node, text[-400:].encode('string_escape')))
263 print "You need to fix these nodes first before fixing instances"
266 for iname in instances:
269 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
271 print "Activating disks for instance '%s'" % iname
273 except errors.GenericError, err:
274 nret, msg = FormatError(err)
276 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
280 for iname, ival in missing.iteritems():
281 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
283 print ("Instance %s cannot be verified as it lives on"
284 " broken nodes" % iname)
286 print "Instance %s has missing logical volumes:" % iname
288 for node, vol in ival:
290 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
292 print ("\t%s /dev/xenvg/%s" % (node, vol))
293 print ("You need to run replace_disks for all the above"
294 " instances, if this message persist after fixing nodes.")
300 def MasterFailover(opts, args):
301 """Failover the master node.
303 This command, when run on a non-master node, will cause the current
304 master to cease being master, and the non-master to become new
308 return bootstrap.MasterFailover()
311 def SearchTags(opts, args):
312 """Searches the tags on all the cluster.
315 op = opcodes.OpSearchTags(pattern=args[0])
316 result = SubmitOpCode(op)
319 result = list(result)
321 for path, tag in result:
322 print "%s %s" % (path, tag)
325 def SetClusterParams(opts, args):
326 """Modify the cluster.
329 opts - class with options as members
332 if not (not opts.lvm_storage or opts.vg_name):
333 print "Please give at least one of the parameters."
336 vg_name = opts.vg_name
337 if not opts.lvm_storage and opts.vg_name:
338 print ("Options --no-lvm-storage and --vg-name conflict.")
341 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
346 # this is an option common to more than one command, so we declare
347 # it here and reuse it
348 node_option = make_option("-n", "--node", action="append", dest="nodes",
349 help="Node to copy to (if not given, all nodes),"
350 " can be given multiple times",
351 metavar="<node>", default=[])
354 'init': (InitCluster, ARGS_ONE,
356 make_option("-s", "--secondary-ip", dest="secondary_ip",
357 help="Specify the secondary ip for this node;"
358 " if given, the entire cluster must have secondary"
360 metavar="ADDRESS", default=None),
361 make_option("-t", "--hypervisor-type", dest="hypervisor_type",
362 help="Specify the hypervisor type "
363 "(xen-pvm, kvm, fake, xen-hvm)",
364 metavar="TYPE", choices=["xen-pvm",
369 make_option("-m", "--mac-prefix", dest="mac_prefix",
370 help="Specify the mac prefix for the instance IP"
371 " addresses, in the format XX:XX:XX",
373 default="aa:00:00",),
374 make_option("-g", "--vg-name", dest="vg_name",
375 help="Specify the volume group name "
376 " (cluster-wide) for disk allocation [xenvg]",
379 make_option("-b", "--bridge", dest="def_bridge",
380 help="Specify the default bridge name (cluster-wide)"
381 " to connect the instances to [%s]" %
382 constants.DEFAULT_BRIDGE,
384 default=constants.DEFAULT_BRIDGE,),
385 make_option("--master-netdev", dest="master_netdev",
386 help="Specify the node interface (cluster-wide)"
387 " on which the master IP address will be added "
388 " [%s]" % constants.DEFAULT_BRIDGE,
390 default=constants.DEFAULT_BRIDGE,),
391 make_option("--file-storage-dir", dest="file_storage_dir",
392 help="Specify the default directory (cluster-wide)"
393 " for storing the file-based disks [%s]" %
394 constants.DEFAULT_FILE_STORAGE_DIR,
396 default=constants.DEFAULT_FILE_STORAGE_DIR,),
397 make_option("--no-lvm-storage", dest="lvm_storage",
398 help="No support for lvm based instances"
400 action="store_false", default=True,),
402 "[opts...] <cluster_name>",
403 "Initialises a new cluster configuration"),
404 'destroy': (DestroyCluster, ARGS_NONE,
406 make_option("--yes-do-it", dest="yes_do_it",
407 help="Destroy cluster",
408 action="store_true"),
410 "", "Destroy cluster"),
411 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
413 "Renames the cluster"),
414 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
415 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
416 help="Skip N+1 memory redundancy tests",
420 "", "Does a check on the cluster configuration"),
421 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
422 "", "Does a check on the cluster disk status"),
423 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
424 "", "Makes the current node the master"),
425 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
426 "", "Shows the cluster version"),
427 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
428 "", "Shows the cluster master"),
429 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
430 "[-n node...] <filename>",
431 "Copies a file to all (or only some) nodes"),
432 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
433 "[-n node...] <command>",
434 "Runs a command on all (or only some) nodes"),
435 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
436 "", "Show cluster configuration"),
437 'list-tags': (ListTags, ARGS_NONE,
438 [DEBUG_OPT], "", "List the tags of the cluster"),
439 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
440 "tag...", "Add tags to the cluster"),
441 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
442 "tag...", "Remove tags from the cluster"),
443 'search-tags': (SearchTags, ARGS_ONE,
444 [DEBUG_OPT], "", "Searches the tags on all objects on"
445 " the cluster for a given pattern (regex)"),
446 'modify': (SetClusterParams, ARGS_NONE,
448 make_option("-g", "--vg-name", dest="vg_name",
449 help="Specify the volume group name "
450 " (cluster-wide) for disk allocation "
451 "and enable lvm based storage",
453 make_option("--no-lvm-storage", dest="lvm_storage",
454 help="Disable support for lvm based instances"
456 action="store_false", default=True,),
459 "Alters the parameters of the cluster"),
462 if __name__ == '__main__':
463 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))