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
22 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
27 from optparse import make_option
30 from ganeti.cli import *
31 from ganeti import opcodes
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti import bootstrap
36 from ganeti import ssh
39 def InitCluster(opts, args):
40 """Initialize the cluster.
43 opts - class with options as members
44 args - list of arguments, expected to be [clustername]
47 if not opts.lvm_storage and opts.vg_name:
48 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
51 vg_name = opts.vg_name
52 if opts.lvm_storage and not opts.vg_name:
53 vg_name = constants.DEFAULT_VG
55 hvlist = opts.enabled_hypervisors
56 if hvlist is not None:
57 hvlist = hvlist.split(",")
59 hvlist = [constants.DEFAULT_ENABLED_HYPERVISOR]
61 # avoid an impossible situation
62 if opts.default_hypervisor in hvlist:
63 default_hypervisor = opts.default_hypervisor
65 default_hypervisor = hvlist[0]
67 hvparams = opts.hvparams
69 # a list of (name, dict) we can pass directly to dict()
70 hvparams = dict(opts.hvparams)
72 # otherwise init as empty dict
75 beparams = opts.beparams
76 # check for invalid parameters
77 for parameter in beparams:
78 if parameter not in constants.BES_PARAMETERS:
79 ToStderr("Invalid backend parameter: %s", parameter)
82 # prepare beparams dict
83 for parameter in constants.BES_PARAMETERS:
84 if parameter not in beparams:
85 beparams[parameter] = constants.BEC_DEFAULTS[parameter]
89 beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
91 ToStderr("%s must be an integer", constants.BE_VCPUS)
94 beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
96 # prepare hvparams dict
97 for hv in constants.HYPER_TYPES:
98 if hv not in hvparams:
100 for parameter in constants.HVC_DEFAULTS[hv]:
101 if parameter not in hvparams[hv]:
102 hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
105 if hv not in constants.HYPER_TYPES:
106 ToStderr("invalid hypervisor: %s", hv)
109 bootstrap.InitCluster(cluster_name=args[0],
110 secondary_ip=opts.secondary_ip,
112 mac_prefix=opts.mac_prefix,
113 def_bridge=opts.def_bridge,
114 master_netdev=opts.master_netdev,
115 file_storage_dir=opts.file_storage_dir,
116 enabled_hypervisors=hvlist,
117 default_hypervisor=default_hypervisor,
123 def DestroyCluster(opts, args):
124 """Destroy the cluster.
127 opts - class with options as members
130 if not opts.yes_do_it:
131 ToStderr("Destroying a cluster is irreversible. If you really want"
132 " destroy this cluster, supply the --yes-do-it option.")
135 op = opcodes.OpDestroyCluster()
136 master = SubmitOpCode(op)
137 # if we reached this, the opcode didn't fail; we can proceed to
138 # shutdown all the daemons
139 bootstrap.FinalizeClusterDestroy(master)
143 def RenameCluster(opts, args):
144 """Rename the cluster.
147 opts - class with options as members, we use force only
148 args - list of arguments, expected to be [new_name]
153 usertext = ("This will rename the cluster to '%s'. If you are connected"
154 " over the network to the cluster name, the operation is very"
155 " dangerous as the IP address will be removed from the node"
156 " and the change may not go through. Continue?") % name
157 if not AskUser(usertext):
160 op = opcodes.OpRenameCluster(name=name)
165 def ShowClusterVersion(opts, args):
166 """Write version of ganeti software to the standard output.
169 opts - class with options as members
172 op = opcodes.OpQueryClusterInfo()
173 result = SubmitOpCode(op)
174 ToStdout("Software version: %s", result["software_version"])
175 ToStdout("Internode protocol: %s", result["protocol_version"])
176 ToStdout("Configuration format: %s", result["config_version"])
177 ToStdout("OS api version: %s", result["os_api_version"])
178 ToStdout("Export interface: %s", result["export_version"])
182 def ShowClusterMaster(opts, args):
183 """Write name of master node to the standard output.
186 opts - class with options as members
189 ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
193 def ShowClusterConfig(opts, args):
194 """Shows cluster information.
197 op = opcodes.OpQueryClusterInfo()
198 result = SubmitOpCode(op)
200 ToStdout("Cluster name: %s", result["name"])
202 ToStdout("Master node: %s", result["master"])
204 ToStdout("Architecture (this node): %s (%s)",
205 result["architecture"][0], result["architecture"][1])
207 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
208 ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
210 ToStdout("Hypervisor parameters:")
211 for hv_name, hv_dict in result["hvparams"].items():
212 ToStdout(" - %s:", hv_name)
213 for item, val in hv_dict.iteritems():
214 ToStdout(" %s: %s", item, val)
216 ToStdout("Cluster parameters:")
217 for gr_name, gr_dict in result["beparams"].items():
218 ToStdout(" - %s:", gr_name)
219 for item, val in gr_dict.iteritems():
220 ToStdout(" %s: %s", item, val)
225 def ClusterCopyFile(opts, args):
226 """Copy a file from master to some nodes.
229 opts - class with options as members
230 args - list containing a single element, the file name
232 nodes - list containing the name of target nodes; if empty, all nodes
236 if not os.path.exists(filename):
237 raise errors.OpPrereqError("No such filename '%s'" % filename)
241 myname = utils.HostInfo().name
243 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
245 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
246 results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
248 srun = ssh.SshRunner(cluster_name=cluster_name)
250 if not srun.CopyFileToNode(node, filename):
251 ToStderr("Copy of file %s to node %s failed", filename, node)
256 def RunClusterCommand(opts, args):
257 """Run a command on some nodes.
260 opts - class with options as members
261 args - the command list as a list
263 nodes: list containing the name of target nodes; if empty, all nodes
268 command = " ".join(args)
269 op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
270 nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
272 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
275 srun = ssh.SshRunner(cluster_name=cluster_name)
277 # Make sure master node is at list end
278 if master_node in nodes:
279 nodes.remove(master_node)
280 nodes.append(master_node)
283 result = srun.Run(name, "root", command)
284 ToStdout("------------------------------------------------")
285 ToStdout("node: %s", name)
286 ToStdout("%s", result.output)
287 ToStdout("return code = %s", result.exit_code)
292 def VerifyCluster(opts, args):
293 """Verify integrity of cluster, performing various test on nodes.
296 opts - class with options as members
300 if opts.skip_nplusone_mem:
301 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
302 op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
309 def VerifyDisks(opts, args):
310 """Verify integrity of cluster disks.
313 opts - class with options as members
316 op = opcodes.OpVerifyDisks()
317 result = SubmitOpCode(op)
318 if not isinstance(result, (list, tuple)) or len(result) != 4:
319 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
321 nodes, nlvm, instances, missing = result
324 ToStdout("Nodes unreachable or with bad data:")
326 ToStdout("\t%s", name)
327 retcode = constants.EXIT_SUCCESS
330 for node, text in nlvm.iteritems():
331 ToStdout("Error on node %s: LVM error: %s",
332 node, text[-400:].encode('string_escape'))
334 ToStdout("You need to fix these nodes first before fixing instances")
337 for iname in instances:
340 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
342 ToStdout("Activating disks for instance '%s'", iname)
344 except errors.GenericError, err:
345 nret, msg = FormatError(err)
347 ToStderr("Error activating disks for instance %s: %s", iname, msg)
350 for iname, ival in missing.iteritems():
351 all_missing = utils.all(ival, lambda x: x[0] in nlvm)
353 ToStdout("Instance %s cannot be verified as it lives on"
354 " broken nodes", iname)
356 ToStdout("Instance %s has missing logical volumes:", iname)
358 for node, vol in ival:
360 ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
362 ToStdout("\t%s /dev/xenvg/%s", node, vol)
363 ToStdout("You need to run replace_disks for all the above"
364 " instances, if this message persist after fixing nodes.")
370 def MasterFailover(opts, args):
371 """Failover the master node.
373 This command, when run on a non-master node, will cause the current
374 master to cease being master, and the non-master to become new
378 return bootstrap.MasterFailover()
381 def SearchTags(opts, args):
382 """Searches the tags on all the cluster.
385 op = opcodes.OpSearchTags(pattern=args[0])
386 result = SubmitOpCode(op)
389 result = list(result)
391 for path, tag in result:
392 ToStdout("%s %s", path, tag)
395 def SetClusterParams(opts, args):
396 """Modify the cluster.
399 opts - class with options as members
402 if not (not opts.lvm_storage or opts.vg_name or
403 opts.enabled_hypervisors or opts.hvparams or
405 ToStderr("Please give at least one of the parameters.")
408 vg_name = opts.vg_name
409 if not opts.lvm_storage and opts.vg_name:
410 ToStdout("Options --no-lvm-storage and --vg-name conflict.")
413 hvlist = opts.enabled_hypervisors
414 if hvlist is not None:
415 hvlist = hvlist.split(",")
417 hvparams = opts.hvparams
419 # a list of (name, dict) we can pass directly to dict()
420 hvparams = dict(opts.hvparams)
422 beparams = opts.beparams
424 op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
425 enabled_hypervisors=hvlist,
432 def QueueOps(opts, args):
438 if command in ("drain", "undrain"):
439 drain_flag = command == "drain"
440 client.SetQueueDrainFlag(drain_flag)
441 elif command == "info":
442 result = client.QueryConfigValues(["drain_flag"])
447 ToStdout("The drain flag is %s" % val)
450 # this is an option common to more than one command, so we declare
451 # it here and reuse it
452 node_option = make_option("-n", "--node", action="append", dest="nodes",
453 help="Node to copy to (if not given, all nodes),"
454 " can be given multiple times",
455 metavar="<node>", default=[])
458 'init': (InitCluster, ARGS_ONE,
460 make_option("-s", "--secondary-ip", dest="secondary_ip",
461 help="Specify the secondary ip for this node;"
462 " if given, the entire cluster must have secondary"
464 metavar="ADDRESS", default=None),
465 make_option("-m", "--mac-prefix", dest="mac_prefix",
466 help="Specify the mac prefix for the instance IP"
467 " addresses, in the format XX:XX:XX",
469 default="aa:00:00",),
470 make_option("-g", "--vg-name", dest="vg_name",
471 help="Specify the volume group name "
472 " (cluster-wide) for disk allocation [xenvg]",
475 make_option("-b", "--bridge", dest="def_bridge",
476 help="Specify the default bridge name (cluster-wide)"
477 " to connect the instances to [%s]" %
478 constants.DEFAULT_BRIDGE,
480 default=constants.DEFAULT_BRIDGE,),
481 make_option("--master-netdev", dest="master_netdev",
482 help="Specify the node interface (cluster-wide)"
483 " on which the master IP address will be added "
484 " [%s]" % constants.DEFAULT_BRIDGE,
486 default=constants.DEFAULT_BRIDGE,),
487 make_option("--file-storage-dir", dest="file_storage_dir",
488 help="Specify the default directory (cluster-wide)"
489 " for storing the file-based disks [%s]" %
490 constants.DEFAULT_FILE_STORAGE_DIR,
492 default=constants.DEFAULT_FILE_STORAGE_DIR,),
493 make_option("--no-lvm-storage", dest="lvm_storage",
494 help="No support for lvm based instances"
496 action="store_false", default=True,),
497 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
498 help="Comma-separated list of hypervisors",
499 type="string", default=None),
500 make_option("-t", "--default-hypervisor",
501 dest="default_hypervisor",
502 help="Default hypervisor to use for instance creation",
503 choices=list(constants.HYPER_TYPES),
504 default=constants.DEFAULT_ENABLED_HYPERVISOR),
505 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
506 help="Hypervisor and hypervisor options, in the"
508 " hypervisor:option=value,option=value,...",
512 keyval_option("-B", "--backend-parameters", dest="beparams",
513 type="keyval", default={},
514 help="Backend parameters"),
516 "[opts...] <cluster_name>",
517 "Initialises a new cluster configuration"),
518 'destroy': (DestroyCluster, ARGS_NONE,
520 make_option("--yes-do-it", dest="yes_do_it",
521 help="Destroy cluster",
522 action="store_true"),
524 "", "Destroy cluster"),
525 'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
527 "Renames the cluster"),
528 'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
529 make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
530 help="Skip N+1 memory redundancy tests",
534 "", "Does a check on the cluster configuration"),
535 'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
536 "", "Does a check on the cluster disk status"),
537 'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
538 "", "Makes the current node the master"),
539 'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
540 "", "Shows the cluster version"),
541 'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
542 "", "Shows the cluster master"),
543 'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
544 "[-n node...] <filename>",
545 "Copies a file to all (or only some) nodes"),
546 'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
547 "[-n node...] <command>",
548 "Runs a command on all (or only some) nodes"),
549 'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
550 "", "Show cluster configuration"),
551 'list-tags': (ListTags, ARGS_NONE,
552 [DEBUG_OPT], "", "List the tags of the cluster"),
553 'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
554 "tag...", "Add tags to the cluster"),
555 'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
556 "tag...", "Remove tags from the cluster"),
557 'search-tags': (SearchTags, ARGS_ONE,
558 [DEBUG_OPT], "", "Searches the tags on all objects on"
559 " the cluster for a given pattern (regex)"),
560 'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
561 "drain|undrain|info", "Change queue properties"),
562 'modify': (SetClusterParams, ARGS_NONE,
564 make_option("-g", "--vg-name", dest="vg_name",
565 help="Specify the volume group name "
566 " (cluster-wide) for disk allocation "
567 "and enable lvm based storage",
569 make_option("--no-lvm-storage", dest="lvm_storage",
570 help="Disable support for lvm based instances"
572 action="store_false", default=True,),
573 make_option("--enabled-hypervisors", dest="enabled_hypervisors",
574 help="Comma-separated list of hypervisors",
575 type="string", default=None),
576 ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
577 help="Hypervisor and hypervisor options, in the"
579 " hypervisor:option=value,option=value,...",
583 keyval_option("-B", "--backend-parameters", dest="beparams",
584 type="keyval", default={},
585 help="Backend parameters"),
588 "Alters the parameters of the cluster"),
591 if __name__ == '__main__':
592 sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))