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()
81 def RenameCluster(opts, args):
82 """Rename the cluster.
85 opts - class with options as members, we use force only
86 args - list of arguments, expected to be [new_name]
91 usertext = ("This will rename the cluster to '%s'. If you are connected"
92 " over the network to the cluster name, the operation is very"
93 " dangerous as the IP address will be removed from the node"
94 " and the change may not go through. Continue?") % name
95 if not AskUser(usertext):
98 op = opcodes.OpRenameCluster(name=name)
103 def ShowClusterVersion(opts, args):
104 """Write version of ganeti software to the standard output.
107 opts - class with options as members
110 op = opcodes.OpQueryClusterInfo()
111 result = SubmitOpCode(op)
112 print ("Software version: %s" % result["software_version"])
113 print ("Internode protocol: %s" % result["protocol_version"])
114 print ("Configuration format: %s" % result["config_version"])
115 print ("OS api version: %s" % result["os_api_version"])
116 print ("Export interface: %s" % result["export_version"])
120 def ShowClusterMaster(opts, args):
121 """Write name of master node to the standard output.
124 opts - class with options as members
127 op = opcodes.OpQueryClusterInfo()
128 result = SubmitOpCode(op)
129 print (result["master"])
133 def ShowClusterConfig(opts, args):
134 """Shows cluster information.
137 op = opcodes.OpQueryClusterInfo()
138 result = SubmitOpCode(op)
140 print ("Cluster name: %s" % result["name"])
142 print ("Master node: %s" % result["master"])
144 print ("Architecture (this node): %s (%s)" %
145 (result["architecture"][0], result["architecture"][1]))
147 print ("Cluster hypervisor: %s" % result["hypervisor_type"])
152 def ClusterCopyFile(opts, args):
153 """Copy a file from master to some nodes.
156 opts - class with options as members
157 args - list containing a single element, the file name
159 nodes - list containing the name of target nodes; if empty, all nodes
163 if not os.path.exists(filename):
164 raise errors.OpPrereqError("No such filename '%s'" % filename)
166 myname = utils.HostInfo().name
168 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
169 results = [row[0] for row in SubmitOpCode(op) if row[0] != myname]
170 srun = ssh.SshRunner()
172 if not srun.CopyFileToNode(node, filename):
173 print >> sys.stderr, ("Copy of file %s to node %s failed" %
179 def RunClusterCommand(opts, args):
180 """Run a command on some nodes.
183 opts - class with options as members
184 args - the command list as a list
186 nodes: list containing the name of target nodes; if empty, all nodes
189 command = " ".join(args)
190 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
191 nodes = [row[0] for row in SubmitOpCode(op)]
193 sstore = ssconf.SimpleStore()
194 master_node = sstore.GetMasterNode()
195 srun = ssh.SshRunner(sstore=sstore)
197 if master_node in nodes:
198 nodes.remove(master_node)
199 nodes.append(master_node)
202 result = srun.Run(name, "root", command)
203 print ("------------------------------------------------")
204 print ("node: %s" % name)
205 print ("%s" % result.output)
206 print ("return code = %s" % result.exit_code)
211 def VerifyCluster(opts, args):
212 """Verify integrity of cluster, performing various test on nodes.
215 opts - class with options as members
219 if opts.skip_nplusone_mem:
220 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
221 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
222 result = SubmitOpCode(op)
226 def VerifyDisks(opts, args):
227 """Verify integrity of cluster disks.
230 opts - class with options as members
233 op = opcodes.OpVerifyDisks()
234 result = SubmitOpCode(op)
235 if not isinstance(result, tuple) or len(result) != 4:
236 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
238 nodes, nlvm, instances, missing = result
241 print "Nodes unreachable or with bad data:"
244 retcode = constants.EXIT_SUCCESS
247 for node, text in nlvm.iteritems():
248 print ("Error on node %s: LVM error: %s" %
249 (node, text[-400:].encode('string_escape')))
251 print "You need to fix these nodes first before fixing instances"
254 for iname in instances:
257 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
259 print "Activating disks for instance '%s'" % iname
261 except errors.GenericError, err:
262 nret, msg = FormatError(err)
264 print >> sys.stderr, ("Error activating disks for instance %s: %s" %
268 for iname, ival in missing.iteritems():
269 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
271 print ("Instance %s cannot be verified as it lives on"
272 " broken nodes" % iname)
274 print "Instance %s has missing logical volumes:" % iname
276 for node, vol in ival:
278 print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
280 print ("\t%s /dev/xenvg/%s" % (node, vol))
281 print ("You need to run replace_disks for all the above"
282 " instances, if this message persist after fixing nodes.")
288 def MasterFailover(opts, args):
289 """Failover the master node.
291 This command, when run on a non-master node, will cause the current
292 master to cease being master, and the non-master to become new
296 return bootstrap.MasterFailover()
299 def SearchTags(opts, args):
300 """Searches the tags on all the cluster.
303 op = opcodes.OpSearchTags(pattern=args[0])
304 result = SubmitOpCode(op)
307 result = list(result)
309 for path, tag in result:
310 print "%s %s" % (path, tag)
313 def SetClusterParams(opts, args):
314 """Modify the cluster.
317 opts - class with options as members
320 if not (not opts.lvm_storage or opts.vg_name):
321 print "Please give at least one of the parameters."
324 vg_name = opts.vg_name
325 if not opts.lvm_storage and opts.vg_name:
326 print ("Options --no-lvm-storage and --vg-name conflict.")
329 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
334 # this is an option common to more than one command, so we declare
335 # it here and reuse it
336 node_option = make_option("-n", "--node", action="append", dest="nodes",
337 help="Node to copy to (if not given, all nodes),"
338 " can be given multiple times",
339 metavar="<node>", default=[])
342 'init': (InitCluster, ARGS_ONE,
344 make_option("-s", "--secondary-ip", dest="secondary_ip",
345 help="Specify the secondary ip for this node;"
346 " if given, the entire cluster must have secondary"
348 metavar="ADDRESS", default=None),
349 make_option("-t", "--hypervisor-type", dest="hypervisor_type",
350 help="Specify the hypervisor type "
351 "(xen-3.0, fake, xen-hvm-3.1)",
352 metavar="TYPE", choices=["xen-3.0",
356 make_option("-m", "--mac-prefix", dest="mac_prefix",
357 help="Specify the mac prefix for the instance IP"
358 " addresses, in the format XX:XX:XX",
360 default="aa:00:00",),
361 make_option("-g", "--vg-name", dest="vg_name",
362 help="Specify the volume group name "
363 " (cluster-wide) for disk allocation [xenvg]",
366 make_option("-b", "--bridge", dest="def_bridge",
367 help="Specify the default bridge name (cluster-wide)"
368 " to connect the instances to [%s]" %
369 constants.DEFAULT_BRIDGE,
371 default=constants.DEFAULT_BRIDGE,),
372 make_option("--master-netdev", dest="master_netdev",
373 help="Specify the node interface (cluster-wide)"
374 " on which the master IP address will be added "
375 " [%s]" % constants.DEFAULT_BRIDGE,
377 default=constants.DEFAULT_BRIDGE,),
378 make_option("--file-storage-dir", dest="file_storage_dir",
379 help="Specify the default directory (cluster-wide)"
380 " for storing the file-based disks [%s]" %
381 constants.DEFAULT_FILE_STORAGE_DIR,
383 default=constants.DEFAULT_FILE_STORAGE_DIR,),
384 make_option("--no-lvm-storage", dest="lvm_storage",
385 help="No support for lvm based instances"
387 action="store_false", default=True,),
389 "[opts...] <cluster_name>",
390 "Initialises a new cluster configuration"),
391 'destroy': (DestroyCluster, ARGS_NONE,
393 make_option("--yes-do-it", dest="yes_do_it",
394 help="Destroy cluster",
395 action="store_true"),
397 "", "Destroy cluster"),
398 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
400 "Renames the cluster"),
401 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
402 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
403 help="Skip N+1 memory redundancy tests",
407 "", "Does a check on the cluster configuration"),
408 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
409 "", "Does a check on the cluster disk status"),
410 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
411 "", "Makes the current node the master"),
412 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
413 "", "Shows the cluster version"),
414 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
415 "", "Shows the cluster master"),
416 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
417 "[-n node...] <filename>",
418 "Copies a file to all (or only some) nodes"),
419 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
420 "[-n node...] <command>",
421 "Runs a command on all (or only some) nodes"),
422 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
423 "", "Show cluster configuration"),
424 'list-tags': (ListTags, ARGS_NONE,
425 [DEBUG_OPT], "", "List the tags of the cluster"),
426 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
427 "tag...", "Add tags to the cluster"),
428 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
429 "tag...", "Remove tags from the cluster"),
430 'search-tags': (SearchTags, ARGS_ONE,
431 [DEBUG_OPT], "", "Searches the tags on all objects on"
432 " the cluster for a given pattern (regex)"),
433 'modify': (SetClusterParams, ARGS_NONE,
435 make_option("-g", "--vg-name", dest="vg_name",
436 help="Specify the volume group name "
437 " (cluster-wide) for disk allocation "
438 "and enable lvm based storage",
440 make_option("--no-lvm-storage", dest="lvm_storage",
441 help="Disable support for lvm based instances"
443 action="store_false", default=True,),
446 "Alters the parameters of the cluster"),
449 if __name__ == '__main__':
450 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))