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 sstore = ssconf.SimpleStore()
131 print sstore.GetMasterNode()
135 def ShowClusterConfig(opts, args):
136 """Shows cluster information.
139 op = opcodes.OpQueryClusterInfo()
140 result = SubmitOpCode(op)
142 print ("Cluster name: %s" % result["name"])
144 print ("Master node: %s" % result["master"])
146 print ("Architecture (this node): %s (%s)" %
147 (result["architecture"][0], result["architecture"][1]))
149 print ("Cluster hypervisor: %s" % result["hypervisor_type"])
154 def ClusterCopyFile(opts, args):
155 """Copy a file from master to some nodes.
158 opts - class with options as members
159 args - list containing a single element, the file name
161 nodes - list containing the name of target nodes; if empty, all nodes
165 if not os.path.exists(filename):
166 raise errors.OpPrereqError("No such filename '%s'" % filename)
168 myname = utils.HostInfo().name
170 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
171 results = [row[0] for row in SubmitOpCode(op) if row[0] != myname]
172 srun = ssh.SshRunner()
174 if not srun.CopyFileToNode(node, filename):
175 print >> sys.stderr, ("Copy of file %s to node %s failed" %
181 def RunClusterCommand(opts, args):
182 """Run a command on some nodes.
185 opts - class with options as members
186 args - the command list as a list
188 nodes: list containing the name of target nodes; if empty, all nodes
191 command = " ".join(args)
192 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
193 nodes = [row[0] for row in SubmitOpCode(op)]
195 sstore = ssconf.SimpleStore()
196 master_node = sstore.GetMasterNode()
197 srun = ssh.SshRunner(sstore=sstore)
199 if master_node in nodes:
200 nodes.remove(master_node)
201 nodes.append(master_node)
204 result = srun.Run(name, "root", command)
205 print ("------------------------------------------------")
206 print ("node: %s" % name)
207 print ("%s" % result.output)
208 print ("return code = %s" % result.exit_code)
213 def VerifyCluster(opts, args):
214 """Verify integrity of cluster, performing various test on nodes.
217 opts - class with options as members
221 if opts.skip_nplusone_mem:
222 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
223 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
224 result = SubmitOpCode(op)
228 def VerifyDisks(opts, args):
229 """Verify integrity of cluster disks.
232 opts - class with options as members
235 op = opcodes.OpVerifyDisks()
236 result = SubmitOpCode(op)
237 if not isinstance(result, tuple) or len(result) != 4:
238 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
240 nodes, nlvm, instances, missing = result
243 print "Nodes unreachable or with bad data:"
246 retcode = constants.EXIT_SUCCESS
249 for node, text in nlvm.iteritems():
250 print ("Error on node %s: LVM error: %s" %
251 (node, text[-400:].encode('string_escape')))
253 print "You need to fix these nodes first before fixing instances"
256 for iname in instances:
259 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
261 print "Activating disks for instance '%s'" % iname
263 except errors.GenericError, err:
264 nret, msg = FormatError(err)
266 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
270 for iname, ival in missing.iteritems():
271 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
273 print ("Instance %s cannot be verified as it lives on"
274 " broken nodes" % iname)
276 print "Instance %s has missing logical volumes:" % iname
278 for node, vol in ival:
280 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
282 print ("\t%s /dev/xenvg/%s" % (node, vol))
283 print ("You need to run replace_disks for all the above"
284 " instances, if this message persist after fixing nodes.")
290 def MasterFailover(opts, args):
291 """Failover the master node.
293 This command, when run on a non-master node, will cause the current
294 master to cease being master, and the non-master to become new
298 return bootstrap.MasterFailover()
301 def SearchTags(opts, args):
302 """Searches the tags on all the cluster.
305 op = opcodes.OpSearchTags(pattern=args[0])
306 result = SubmitOpCode(op)
309 result = list(result)
311 for path, tag in result:
312 print "%s %s" % (path, tag)
315 def SetClusterParams(opts, args):
316 """Modify the cluster.
319 opts - class with options as members
322 if not (not opts.lvm_storage or opts.vg_name):
323 print "Please give at least one of the parameters."
326 vg_name = opts.vg_name
327 if not opts.lvm_storage and opts.vg_name:
328 print ("Options --no-lvm-storage and --vg-name conflict.")
331 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
336 # this is an option common to more than one command, so we declare
337 # it here and reuse it
338 node_option = make_option("-n", "--node", action="append", dest="nodes",
339 help="Node to copy to (if not given, all nodes),"
340 " can be given multiple times",
341 metavar="<node>", default=[])
344 'init': (InitCluster, ARGS_ONE,
346 make_option("-s", "--secondary-ip", dest="secondary_ip",
347 help="Specify the secondary ip for this node;"
348 " if given, the entire cluster must have secondary"
350 metavar="ADDRESS", default=None),
351 make_option("-t", "--hypervisor-type", dest="hypervisor_type",
352 help="Specify the hypervisor type "
353 "(xen-3.0, fake, xen-hvm-3.1)",
354 metavar="TYPE", choices=["xen-3.0",
358 make_option("-m", "--mac-prefix", dest="mac_prefix",
359 help="Specify the mac prefix for the instance IP"
360 " addresses, in the format XX:XX:XX",
362 default="aa:00:00",),
363 make_option("-g", "--vg-name", dest="vg_name",
364 help="Specify the volume group name "
365 " (cluster-wide) for disk allocation [xenvg]",
368 make_option("-b", "--bridge", dest="def_bridge",
369 help="Specify the default bridge name (cluster-wide)"
370 " to connect the instances to [%s]" %
371 constants.DEFAULT_BRIDGE,
373 default=constants.DEFAULT_BRIDGE,),
374 make_option("--master-netdev", dest="master_netdev",
375 help="Specify the node interface (cluster-wide)"
376 " on which the master IP address will be added "
377 " [%s]" % constants.DEFAULT_BRIDGE,
379 default=constants.DEFAULT_BRIDGE,),
380 make_option("--file-storage-dir", dest="file_storage_dir",
381 help="Specify the default directory (cluster-wide)"
382 " for storing the file-based disks [%s]" %
383 constants.DEFAULT_FILE_STORAGE_DIR,
385 default=constants.DEFAULT_FILE_STORAGE_DIR,),
386 make_option("--no-lvm-storage", dest="lvm_storage",
387 help="No support for lvm based instances"
389 action="store_false", default=True,),
391 "[opts...] <cluster_name>",
392 "Initialises a new cluster configuration"),
393 'destroy': (DestroyCluster, ARGS_NONE,
395 make_option("--yes-do-it", dest="yes_do_it",
396 help="Destroy cluster",
397 action="store_true"),
399 "", "Destroy cluster"),
400 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
402 "Renames the cluster"),
403 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
404 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
405 help="Skip N+1 memory redundancy tests",
409 "", "Does a check on the cluster configuration"),
410 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
411 "", "Does a check on the cluster disk status"),
412 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
413 "", "Makes the current node the master"),
414 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
415 "", "Shows the cluster version"),
416 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
417 "", "Shows the cluster master"),
418 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
419 "[-n node...] <filename>",
420 "Copies a file to all (or only some) nodes"),
421 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
422 "[-n node...] <command>",
423 "Runs a command on all (or only some) nodes"),
424 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
425 "", "Show cluster configuration"),
426 'list-tags': (ListTags, ARGS_NONE,
427 [DEBUG_OPT], "", "List the tags of the cluster"),
428 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
429 "tag...", "Add tags to the cluster"),
430 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
431 "tag...", "Remove tags from the cluster"),
432 'search-tags': (SearchTags, ARGS_ONE,
433 [DEBUG_OPT], "", "Searches the tags on all objects on"
434 " the cluster for a given pattern (regex)"),
435 'modify': (SetClusterParams, ARGS_NONE,
437 make_option("-g", "--vg-name", dest="vg_name",
438 help="Specify the volume group name "
439 " (cluster-wide) for disk allocation "
440 "and enable lvm based storage",
442 make_option("--no-lvm-storage", dest="lvm_storage",
443 help="Disable support for lvm based instances"
445 action="store_false", default=True,),
448 "Alters the parameters of the cluster"),
451 if __name__ == '__main__':
452 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))