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
31 from ganeti import bootstrap
32 from ganeti import ssh
33 from ganeti import ssconf
36 def InitCluster(opts, args):
37 """Initialize the cluster.
40 opts - class with options as members
41 args - list of arguments, expected to be [clustername]
44 if not opts.lvm_storage and opts.vg_name:
45 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
48 vg_name = opts.vg_name
49 if opts.lvm_storage and not opts.vg_name:
50 vg_name = constants.DEFAULT_VG
52 hvlist = opts.enabled_hypervisors
53 if hvlist is not None:
54 hvlist = hvlist.split(",")
56 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
58 hvparams = opts.hvparams
60 # a list of (name, dict) we can pass directly to dict()
61 hvparams = dict(opts.hvparams)
63 # otherwise init as empty dict
66 beparams = opts.beparams
67 # check for invalid parameters
68 for parameter in beparams:
69 if parameter not in constants.BES_PARAMETERS:
70 print "Invalid backend parameter: %s" % parameter
73 # prepare beparams dict
74 for parameter in constants.BES_PARAMETERS:
75 if parameter not in beparams:
76 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
80 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
82 print "%s must be an integer" % constants.BE_VCPUS
85 beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
87 # prepare hvparams dict
88 for hv in constants.HYPER_TYPES:
89 if hv not in hvparams:
91 for parameter in constants.HVC_DEFAULTS[hv]:
92 if parameter not in hvparams[hv]:
93 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
96 if hv not in constants.HYPER_TYPES:
97 print "invalid hypervisor: %s" % hv
100 bootstrap.InitCluster(cluster_name=args[0],
101 secondary_ip=opts.secondary_ip,
103 mac_prefix=opts.mac_prefix,
104 def_bridge=opts.def_bridge,
105 master_netdev=opts.master_netdev,
106 file_storage_dir=opts.file_storage_dir,
107 enabled_hypervisors=hvlist,
113 def DestroyCluster(opts, args):
114 """Destroy the cluster.
117 opts - class with options as members
120 if not opts.yes_do_it:
121 ToStderr("Destroying a cluster is irreversible. If you really want"
122 " destroy this cluster, supply the --yes-do-it option.")
125 op = opcodes.OpDestroyCluster()
126 master = SubmitOpCode(op)
127 # if we reached this, the opcode didn't fail; we can proceed to
128 # shutdown all the daemons
129 bootstrap.FinalizeClusterDestroy(master)
133 def RenameCluster(opts, args):
134 """Rename the cluster.
137 opts - class with options as members, we use force only
138 args - list of arguments, expected to be [new_name]
143 usertext = ("This will rename the cluster to '%s'. If you are connected"
144 " over the network to the cluster name, the operation is very"
145 " dangerous as the IP address will be removed from the node"
146 " and the change may not go through. Continue?") % name
147 if not AskUser(usertext):
150 op = opcodes.OpRenameCluster(name=name)
155 def ShowClusterVersion(opts, args):
156 """Write version of ganeti software to the standard output.
159 opts - class with options as members
162 op = opcodes.OpQueryClusterInfo()
163 result = SubmitOpCode(op)
164 ToStdout("Software version: %s", result["software_version"])
165 ToStdout("Internode protocol: %s", result["protocol_version"])
166 ToStdout("Configuration format: %s", result["config_version"])
167 ToStdout("OS api version: %s", result["os_api_version"])
168 ToStdout("Export interface: %s", result["export_version"])
172 def ShowClusterMaster(opts, args):
173 """Write name of master node to the standard output.
176 opts - class with options as members
179 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
183 def ShowClusterConfig(opts, args):
184 """Shows cluster information.
187 op = opcodes.OpQueryClusterInfo()
188 result = SubmitOpCode(op)
190 ToStdout("Cluster name: %s", result["name"])
192 ToStdout("Master node: %s", result["master"])
194 ToStdout("Architecture (this node): %s (%s)",
195 result["architecture"][0], result["architecture"][1])
197 ToStdout("Default hypervisor: %s", result["hypervisor_type"])
198 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
200 ToStdout("Hypervisor parameters:")
201 for hv_name, hv_dict in result["hvparams"].items():
202 ToStdout(" - %s:", hv_name)
203 for item, val in hv_dict.iteritems():
204 ToStdout(" %s: %s", item, val)
206 ToStdout("Cluster parameters:")
207 for gr_name, gr_dict in result["beparams"].items():
208 ToStdout(" - %s:", gr_name)
209 for item, val in gr_dict.iteritems():
210 ToStdout(" %s: %s", item, val)
215 def ClusterCopyFile(opts, args):
216 """Copy a file from master to some nodes.
219 opts - class with options as members
220 args - list containing a single element, the file name
222 nodes - list containing the name of target nodes; if empty, all nodes
226 if not os.path.exists(filename):
227 raise errors.OpPrereqError("No such filename '%s'" % filename)
231 myname = utils.HostInfo().name
233 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
235 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
236 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
238 srun = ssh.SshRunner(cluster_name=cluster_name)
240 if not srun.CopyFileToNode(node, filename):
241 ToStderr("Copy of file %s to node %s failed", filename, node)
246 def RunClusterCommand(opts, args):
247 """Run a command on some nodes.
250 opts - class with options as members
251 args - the command list as a list
253 nodes: list containing the name of target nodes; if empty, all nodes
258 command = " ".join(args)
259 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
260 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
262 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
265 srun = ssh.SshRunner(cluster_name=cluster_name)
267 # Make sure master node is at list end
268 if master_node in nodes:
269 nodes.remove(master_node)
270 nodes.append(master_node)
273 result = srun.Run(name, "root", command)
274 ToStdout("------------------------------------------------")
275 ToStdout("node: %s", name)
276 ToStdout("%s", result.output)
277 ToStdout("return code = %s", result.exit_code)
282 def VerifyCluster(opts, args):
283 """Verify integrity of cluster, performing various test on nodes.
286 opts - class with options as members
290 if opts.skip_nplusone_mem:
291 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
292 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
299 def VerifyDisks(opts, args):
300 """Verify integrity of cluster disks.
303 opts - class with options as members
306 op = opcodes.OpVerifyDisks()
307 result = SubmitOpCode(op)
308 if not isinstance(result, (list, tuple)) or len(result) != 4:
309 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
311 nodes, nlvm, instances, missing = result
314 ToStdout("Nodes unreachable or with bad data:")
316 ToStdout("\t%s", name)
317 retcode = constants.EXIT_SUCCESS
320 for node, text in nlvm.iteritems():
321 ToStdout("Error on node %s: LVM error: %s",
322 node, text[-400:].encode('string_escape'))
324 ToStdout("You need to fix these nodes first before fixing instances")
327 for iname in instances:
330 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
332 ToStdout("Activating disks for instance '%s'", iname)
334 except errors.GenericError, err:
335 nret, msg = FormatError(err)
337 ToStderr("Error activating disks for instance %s: %s", iname, msg)
340 for iname, ival in missing.iteritems():
341 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
343 ToStdout("Instance %s cannot be verified as it lives on"
344 " broken nodes", iname)
346 ToStdout("Instance %s has missing logical volumes:", iname)
348 for node, vol in ival:
350 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
352 ToStdout("\t%s /dev/xenvg/%s", node, vol)
353 ToStdout("You need to run replace_disks for all the above"
354 " instances, if this message persist after fixing nodes.")
360 def MasterFailover(opts, args):
361 """Failover the master node.
363 This command, when run on a non-master node, will cause the current
364 master to cease being master, and the non-master to become new
368 return bootstrap.MasterFailover()
371 def SearchTags(opts, args):
372 """Searches the tags on all the cluster.
375 op = opcodes.OpSearchTags(pattern=args[0])
376 result = SubmitOpCode(op)
379 result = list(result)
381 for path, tag in result:
382 ToStdout("%s %s", path, tag)
385 def SetClusterParams(opts, args):
386 """Modify the cluster.
389 opts - class with options as members
392 if not (not opts.lvm_storage or opts.vg_name or
393 opts.enabled_hypervisors or opts.hvparams or
395 ToStderr("Please give at least one of the parameters.")
398 vg_name = opts.vg_name
399 if not opts.lvm_storage and opts.vg_name:
400 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
403 hvlist = opts.enabled_hypervisors
404 if hvlist is not None:
405 hvlist = hvlist.split(",")
407 hvparams = opts.hvparams
409 # a list of (name, dict) we can pass directly to dict()
410 hvparams = dict(opts.hvparams)
412 beparams = opts.beparams
414 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
415 enabled_hypervisors=hvlist,
422 def QueueOps(opts, args):
428 if command in ("drain", "undrain"):
429 drain_flag = command == "drain"
430 client.SetQueueDrainFlag(drain_flag)
431 elif command == "info":
432 result = client.QueryConfigValues(["drain_flag"])
437 ToStdout("The drain flag is %s" % val)
440 # this is an option common to more than one command, so we declare
441 # it here and reuse it
442 node_option = make_option("-n", "--node", action="append", dest="nodes",
443 help="Node to copy to (if not given, all nodes),"
444 " can be given multiple times",
445 metavar="<node>", default=[])
448 'init': (InitCluster, ARGS_ONE,
450 make_option("-s", "--secondary-ip", dest="secondary_ip",
451 help="Specify the secondary ip for this node;"
452 " if given, the entire cluster must have secondary"
454 metavar="ADDRESS", default=None),
455 make_option("-m", "--mac-prefix", dest="mac_prefix",
456 help="Specify the mac prefix for the instance IP"
457 " addresses, in the format XX:XX:XX",
459 default="aa:00:00",),
460 make_option("-g", "--vg-name", dest="vg_name",
461 help="Specify the volume group name "
462 " (cluster-wide) for disk allocation [xenvg]",
465 make_option("-b", "--bridge", dest="def_bridge",
466 help="Specify the default bridge name (cluster-wide)"
467 " to connect the instances to [%s]" %
468 constants.DEFAULT_BRIDGE,
470 default=constants.DEFAULT_BRIDGE,),
471 make_option("--master-netdev", dest="master_netdev",
472 help="Specify the node interface (cluster-wide)"
473 " on which the master IP address will be added "
474 " [%s]" % constants.DEFAULT_BRIDGE,
476 default=constants.DEFAULT_BRIDGE,),
477 make_option("--file-storage-dir", dest="file_storage_dir",
478 help="Specify the default directory (cluster-wide)"
479 " for storing the file-based disks [%s]" %
480 constants.DEFAULT_FILE_STORAGE_DIR,
482 default=constants.DEFAULT_FILE_STORAGE_DIR,),
483 make_option("--no-lvm-storage", dest="lvm_storage",
484 help="No support for lvm based instances"
486 action="store_false", default=True,),
487 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
488 help="Comma-separated list of hypervisors",
489 type="string", default=None),
490 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
491 help="Hypervisor and hypervisor options, in the"
493 " hypervisor:option=value,option=value,...",
497 keyval_option("-B", "--backend-parameters", dest="beparams",
498 type="keyval", default={},
499 help="Backend parameters"),
501 "[opts...] <cluster_name>",
502 "Initialises a new cluster configuration"),
503 'destroy': (DestroyCluster, ARGS_NONE,
505 make_option("--yes-do-it", dest="yes_do_it",
506 help="Destroy cluster",
507 action="store_true"),
509 "", "Destroy cluster"),
510 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
512 "Renames the cluster"),
513 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
514 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
515 help="Skip N+1 memory redundancy tests",
519 "", "Does a check on the cluster configuration"),
520 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
521 "", "Does a check on the cluster disk status"),
522 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
523 "", "Makes the current node the master"),
524 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
525 "", "Shows the cluster version"),
526 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
527 "", "Shows the cluster master"),
528 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
529 "[-n node...] <filename>",
530 "Copies a file to all (or only some) nodes"),
531 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
532 "[-n node...] <command>",
533 "Runs a command on all (or only some) nodes"),
534 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
535 "", "Show cluster configuration"),
536 'list-tags': (ListTags, ARGS_NONE,
537 [DEBUG_OPT], "", "List the tags of the cluster"),
538 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
539 "tag...", "Add tags to the cluster"),
540 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
541 "tag...", "Remove tags from the cluster"),
542 'search-tags': (SearchTags, ARGS_ONE,
543 [DEBUG_OPT], "", "Searches the tags on all objects on"
544 " the cluster for a given pattern (regex)"),
545 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
546 "drain|undrain|info", "Change queue properties"),
547 'modify': (SetClusterParams, ARGS_NONE,
549 make_option("-g", "--vg-name", dest="vg_name",
550 help="Specify the volume group name "
551 " (cluster-wide) for disk allocation "
552 "and enable lvm based storage",
554 make_option("--no-lvm-storage", dest="lvm_storage",
555 help="Disable support for lvm based instances"
557 action="store_false", default=True,),
558 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
559 help="Comma-separated list of hypervisors",
560 type="string", default=None),
561 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
562 help="Hypervisor and hypervisor options, in the"
564 " hypervisor:option=value,option=value,...",
568 keyval_option("-B", "--backend-parameters", dest="beparams",
569 type="keyval", default={},
570 help="Backend parameters"),
573 "Alters the parameters of the cluster"),
576 if __name__ == '__main__':
577 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))