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
26 from ganeti.cli import *
27 from ganeti import opcodes
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import utils
33 def InitCluster(opts, args):
34 """Initialize the cluster.
37 opts - class with options as members
38 args - list of arguments, expected to be [clustername]
41 if not opts.lvm_storage and opts.vg_name:
42 print ("Options --no-lvm-storage and --vg-name conflict.")
45 vg_name = opts.vg_name
46 if opts.lvm_storage and not opts.vg_name:
47 vg_name = constants.DEFAULT_VG
49 op = opcodes.OpInitCluster(cluster_name=args[0],
50 secondary_ip=opts.secondary_ip,
51 hypervisor_type=opts.hypervisor_type,
53 mac_prefix=opts.mac_prefix,
54 def_bridge=opts.def_bridge,
55 master_netdev=opts.master_netdev,
56 file_storage_dir=opts.file_storage_dir)
61 def DestroyCluster(opts, args):
62 """Destroy the cluster.
65 opts - class with options as members
68 if not opts.yes_do_it:
69 print ("Destroying a cluster is irreversibly. If you really want destroy"
70 " this cluster, supply the --yes-do-it option.")
73 op = opcodes.OpDestroyCluster()
78 def RenameCluster(opts, args):
79 """Rename the cluster.
82 opts - class with options as members, we use force only
83 args - list of arguments, expected to be [new_name]
88 usertext = ("This will rename the cluster to '%s'. If you are connected"
89 " over the network to the cluster name, the operation is very"
90 " dangerous as the IP address will be removed from the node"
91 " and the change may not go through. Continue?") % name
92 if not AskUser(usertext):
95 op = opcodes.OpRenameCluster(name=name)
100 def ShowClusterVersion(opts, args):
101 """Write version of ganeti software to the standard output.
104 opts - class with options as members
107 op = opcodes.OpQueryClusterInfo()
108 result = SubmitOpCode(op)
109 print ("Software version: %s" % result["software_version"])
110 print ("Internode protocol: %s" % result["protocol_version"])
111 print ("Configuration format: %s" % result["config_version"])
112 print ("OS api version: %s" % result["os_api_version"])
113 print ("Export interface: %s" % result["export_version"])
117 def ShowClusterMaster(opts, args):
118 """Write name of master node to the standard output.
121 opts - class with options as members
124 op = opcodes.OpQueryClusterInfo()
125 result = SubmitOpCode(op)
126 print (result["master"])
130 def ShowClusterConfig(opts, args):
131 """Shows cluster information.
134 op = opcodes.OpQueryClusterInfo()
135 result = SubmitOpCode(op)
137 print ("Cluster name: %s" % result["name"])
139 print ("Master node: %s" % result["master"])
141 print ("Architecture (this node): %s (%s)" %
142 (result["architecture"][0], result["architecture"][1]))
147 def ClusterCopyFile(opts, args):
148 """Copy a file from master to some nodes.
151 opts - class with options as members
152 args - list containing a single element, the file name
154 nodes - list containing the name of target nodes; if empty, all nodes
157 op = opcodes.OpClusterCopyFile(filename=args[0], nodes=opts.nodes)
162 def RunClusterCommand(opts, args):
163 """Run a command on some nodes.
166 opts - class with options as members
167 args - the command list as a list
169 nodes: list containing the name of target nodes; if empty, all nodes
172 command = " ".join(args)
174 op = opcodes.OpRunClusterCommand(command=command, nodes=nodes)
175 result = SubmitOpCode(op)
176 for node, output, exit_code in result:
177 print ("------------------------------------------------")
178 print ("node: %s" % node)
179 print ("%s" % output)
180 print ("return code = %s" % exit_code)
183 def VerifyCluster(opts, args):
184 """Verify integrity of cluster, performing various test on nodes.
187 opts - class with options as members
191 if opts.skip_nplusone_mem:
192 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
193 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
194 result = SubmitOpCode(op)
198 def VerifyDisks(opts, args):
199 """Verify integrity of cluster disks.
202 opts - class with options as members
205 op = opcodes.OpVerifyDisks()
206 result = SubmitOpCode(op)
207 if not isinstance(result, tuple) or len(result) != 4:
208 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
210 nodes, nlvm, instances, missing = result
213 print "Nodes unreachable or with bad data:"
216 retcode = constants.EXIT_SUCCESS
219 for node, text in nlvm.iteritems():
220 print ("Error on node %s: LVM error: %s" %
221 (node, text[-400:].encode('string_escape')))
223 print "You need to fix these nodes first before fixing instances"
226 for iname in instances:
229 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
231 print "Activating disks for instance '%s'" % iname
233 except errors.GenericError, err:
234 nret, msg = FormatError(err)
236 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
240 for iname, ival in missing.iteritems():
241 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
243 print ("Instance %s cannot be verified as it lives on"
244 " broken nodes" % iname)
246 print "Instance %s has missing logical volumes:" % iname
248 for node, vol in ival:
250 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
252 print ("\t%s /dev/xenvg/%s" % (node, vol))
253 print ("You need to run replace_disks for all the above"
254 " instances, if this message persist after fixing nodes.")
260 def MasterFailover(opts, args):
261 """Failover the master node.
263 This command, when run on a non-master node, will cause the current
264 master to cease being master, and the non-master to become new
268 op = opcodes.OpMasterFailover()
272 def SearchTags(opts, args):
273 """Searches the tags on all the cluster.
276 op = opcodes.OpSearchTags(pattern=args[0])
277 result = SubmitOpCode(op)
280 result = list(result)
282 for path, tag in result:
283 print "%s %s" % (path, tag)
286 def SetClusterParams(opts, args):
287 """Modify the cluster.
290 opts - class with options as members
293 if not (not opts.lvm_storage or opts.vg_name):
294 print "Please give at least one of the parameters."
297 vg_name = opts.vg_name
298 if not opts.lvm_storage and opts.vg_name:
299 print ("Options --no-lvm-storage and --vg-name conflict.")
302 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
307 # this is an option common to more than one command, so we declare
308 # it here and reuse it
309 node_option = make_option("-n", "--node", action="append", dest="nodes",
310 help="Node to copy to (if not given, all nodes),"
311 " can be given multiple times",
312 metavar="<node>", default=[])
315 'init': (InitCluster, ARGS_ONE,
317 make_option("-s", "--secondary-ip", dest="secondary_ip",
318 help="Specify the secondary ip for this node;"
319 " if given, the entire cluster must have secondary"
321 metavar="ADDRESS", default=None),
322 make_option("-t", "--hypervisor-type", dest="hypervisor_type",
323 help="Specify the hypervisor type "
324 "(xen-3.0, fake, xen-hvm-3.1)",
325 metavar="TYPE", choices=["xen-3.0",
329 make_option("-m", "--mac-prefix", dest="mac_prefix",
330 help="Specify the mac prefix for the instance IP"
331 " addresses, in the format XX:XX:XX",
333 default="aa:00:00",),
334 make_option("-g", "--vg-name", dest="vg_name",
335 help="Specify the volume group name "
336 " (cluster-wide) for disk allocation [xenvg]",
339 make_option("-b", "--bridge", dest="def_bridge",
340 help="Specify the default bridge name (cluster-wide)"
341 " to connect the instances to [%s]" %
342 constants.DEFAULT_BRIDGE,
344 default=constants.DEFAULT_BRIDGE,),
345 make_option("--master-netdev", dest="master_netdev",
346 help="Specify the node interface (cluster-wide)"
347 " on which the master IP address will be added "
348 " [%s]" % constants.DEFAULT_BRIDGE,
350 default=constants.DEFAULT_BRIDGE,),
351 make_option("--file-storage-dir", dest="file_storage_dir",
352 help="Specify the default directory (cluster-wide)"
353 " for storing the file-based disks [%s]" %
354 constants.DEFAULT_FILE_STORAGE_DIR,
356 default=constants.DEFAULT_FILE_STORAGE_DIR,),
357 make_option("--no-lvm-storage", dest="lvm_storage",
358 help="No support for lvm based instances"
360 action="store_false", default=True,),
362 "[opts...] <cluster_name>",
363 "Initialises a new cluster configuration"),
364 'destroy': (DestroyCluster, ARGS_NONE,
366 make_option("--yes-do-it", dest="yes_do_it",
367 help="Destroy cluster",
368 action="store_true"),
370 "", "Destroy cluster"),
371 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
373 "Renames the cluster"),
374 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
375 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
376 help="Skip N+1 memory redundancy tests",
380 "", "Does a check on the cluster configuration"),
381 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
382 "", "Does a check on the cluster disk status"),
383 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
384 "", "Makes the current node the master"),
385 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
386 "", "Shows the cluster version"),
387 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
388 "", "Shows the cluster master"),
389 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
390 "[-n node...] <filename>",
391 "Copies a file to all (or only some) nodes"),
392 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
393 "[-n node...] <command>",
394 "Runs a command on all (or only some) nodes"),
395 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
396 "", "Show cluster configuration"),
397 'list-tags': (ListTags, ARGS_NONE,
398 [DEBUG_OPT], "", "List the tags of the cluster"),
399 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
400 "tag...", "Add tags to the cluster"),
401 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
402 "tag...", "Remove tags from the cluster"),
403 'search-tags': (SearchTags, ARGS_ONE,
404 [DEBUG_OPT], "", "Searches the tags on all objects on"
405 " the cluster for a given pattern (regex)"),
406 'modify': (SetClusterParams, ARGS_NONE,
408 make_option("-g", "--vg-name", dest="vg_name",
409 help="Specify the volume group name "
410 " (cluster-wide) for disk allocation "
411 "and enable lvm based storage",
413 make_option("--no-lvm-storage", dest="lvm_storage",
414 help="Disable support for lvm based instances"
416 action="store_false", default=True,),
419 "Alters the parameters of the cluster"),
422 if __name__ == '__main__':
423 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))