Fix gnt-cluster getmaster on non-master nodes
[ganeti-local] / scripts / gnt-cluster
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
25
26 import sys
27 from optparse import make_option
28 import os.path
29
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
37
38
39 @UsesRPC
40 def InitCluster(opts, args):
41   """Initialize the cluster.
42
43   @param opts: the command line options selected by the user
44   @type args: list
45   @param args: should contain only one element, the desired
46       cluster name
47   @rtype: int
48   @return: the desired exit code
49
50   """
51   if not opts.lvm_storage and opts.vg_name:
52     ToStderr("Options --no-lvm-storage and --vg-name conflict.")
53     return 1
54
55   vg_name = opts.vg_name
56   if opts.lvm_storage and not opts.vg_name:
57     vg_name = constants.DEFAULT_VG
58
59   hvlist = opts.enabled_hypervisors
60   if hvlist is not None:
61     hvlist = hvlist.split(",")
62   else:
63     hvlist = [opts.default_hypervisor]
64
65   # avoid an impossible situation
66   if opts.default_hypervisor not in hvlist:
67     ToStderr("The default hypervisor requested (%s) is not"
68              " within the enabled hypervisor list (%s)" %
69              (opts.default_hypervisor, hvlist))
70     return 1
71
72   hvparams = dict(opts.hvparams)
73
74   beparams = opts.beparams
75   # check for invalid parameters
76   for parameter in beparams:
77     if parameter not in constants.BES_PARAMETERS:
78       ToStderr("Invalid backend parameter: %s", parameter)
79       return 1
80
81   # prepare beparams dict
82   for parameter in constants.BES_PARAMETERS:
83     if parameter not in beparams:
84       beparams[parameter] = constants.BEC_DEFAULTS[parameter]
85   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
86
87   # prepare hvparams dict
88   for hv in constants.HYPER_TYPES:
89     if hv not in hvparams:
90       hvparams[hv] = {}
91     for parameter in constants.HVC_DEFAULTS[hv]:
92       if parameter not in hvparams[hv]:
93         hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
94     utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
95
96   for hv in hvlist:
97     if hv not in constants.HYPER_TYPES:
98       ToStderr("invalid hypervisor: %s", hv)
99       return 1
100
101   bootstrap.InitCluster(cluster_name=args[0],
102                         secondary_ip=opts.secondary_ip,
103                         vg_name=vg_name,
104                         mac_prefix=opts.mac_prefix,
105                         def_bridge=opts.def_bridge,
106                         master_netdev=opts.master_netdev,
107                         file_storage_dir=opts.file_storage_dir,
108                         enabled_hypervisors=hvlist,
109                         default_hypervisor=opts.default_hypervisor,
110                         hvparams=hvparams,
111                         beparams=beparams,
112                         candidate_pool_size=opts.candidate_pool_size,
113                         )
114   return 0
115
116
117 @UsesRPC
118 def DestroyCluster(opts, args):
119   """Destroy the cluster.
120
121   @param opts: the command line options selected by the user
122   @type args: list
123   @param args: should be an empty list
124   @rtype: int
125   @return: the desired exit code
126
127   """
128   if not opts.yes_do_it:
129     ToStderr("Destroying a cluster is irreversible. If you really want"
130              " destroy this cluster, supply the --yes-do-it option.")
131     return 1
132
133   op = opcodes.OpDestroyCluster()
134   master = SubmitOpCode(op)
135   # if we reached this, the opcode didn't fail; we can proceed to
136   # shutdown all the daemons
137   bootstrap.FinalizeClusterDestroy(master)
138   return 0
139
140
141 def RenameCluster(opts, args):
142   """Rename the cluster.
143
144   @param opts: the command line options selected by the user
145   @type args: list
146   @param args: should contain only one element, the new cluster name
147   @rtype: int
148   @return: the desired exit code
149
150   """
151   name = args[0]
152   if not opts.force:
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):
158       return 1
159
160   op = opcodes.OpRenameCluster(name=name)
161   SubmitOpCode(op)
162   return 0
163
164
165 def RedistributeConfig(opts, args):
166   """Forces push of the cluster configuration.
167
168   @param opts: the command line options selected by the user
169   @type args: list
170   @param args: empty list
171   @rtype: int
172   @return: the desired exit code
173
174   """
175   op = opcodes.OpRedistributeConfig()
176   SubmitOrSend(op, opts)
177   return 0
178
179
180 def ShowClusterVersion(opts, args):
181   """Write version of ganeti software to the standard output.
182
183   @param opts: the command line options selected by the user
184   @type args: list
185   @param args: should be an empty list
186   @rtype: int
187   @return: the desired exit code
188
189   """
190   cl = GetClient()
191   result = cl.QueryClusterInfo()
192   ToStdout("Software version: %s", result["software_version"])
193   ToStdout("Internode protocol: %s", result["protocol_version"])
194   ToStdout("Configuration format: %s", result["config_version"])
195   ToStdout("OS api version: %s", result["os_api_version"])
196   ToStdout("Export interface: %s", result["export_version"])
197   return 0
198
199
200 def ShowClusterMaster(opts, args):
201   """Write name of master node to the standard output.
202
203   @param opts: the command line options selected by the user
204   @type args: list
205   @param args: should be an empty list
206   @rtype: int
207   @return: the desired exit code
208
209   """
210   master = bootstrap.GetMaster()
211   ToStdout(master)
212   return 0
213
214
215 def ShowClusterConfig(opts, args):
216   """Shows cluster information.
217
218   @param opts: the command line options selected by the user
219   @type args: list
220   @param args: should be an empty list
221   @rtype: int
222   @return: the desired exit code
223
224   """
225   cl = GetClient()
226   result = cl.QueryClusterInfo()
227
228   ToStdout("Cluster name: %s", result["name"])
229
230   ToStdout("Master node: %s", result["master"])
231
232   ToStdout("Architecture (this node): %s (%s)",
233            result["architecture"][0], result["architecture"][1])
234
235   ToStdout("Default hypervisor: %s", result["default_hypervisor"])
236   ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
237
238   ToStdout("Hypervisor parameters:")
239   for hv_name, hv_dict in result["hvparams"].items():
240     ToStdout("  - %s:", hv_name)
241     for item, val in hv_dict.iteritems():
242       ToStdout("      %s: %s", item, val)
243
244   ToStdout("Cluster parameters:")
245   ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
246
247   ToStdout("Default instance parameters:")
248   for gr_name, gr_dict in result["beparams"].items():
249     ToStdout("  - %s:", gr_name)
250     for item, val in gr_dict.iteritems():
251       ToStdout("      %s: %s", item, val)
252
253   return 0
254
255
256 def ClusterCopyFile(opts, args):
257   """Copy a file from master to some nodes.
258
259   @param opts: the command line options selected by the user
260   @type args: list
261   @param args: should contain only one element, the path of
262       the file to be copied
263   @rtype: int
264   @return: the desired exit code
265
266   """
267   filename = args[0]
268   if not os.path.exists(filename):
269     raise errors.OpPrereqError("No such filename '%s'" % filename)
270
271   cl = GetClient()
272
273   myname = utils.HostInfo().name
274
275   cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
276
277   results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
278   results = [name for name in results if name != myname]
279
280   srun = ssh.SshRunner(cluster_name=cluster_name)
281   for node in results:
282     if not srun.CopyFileToNode(node, filename):
283       ToStderr("Copy of file %s to node %s failed", filename, node)
284
285   return 0
286
287
288 def RunClusterCommand(opts, args):
289   """Run a command on some nodes.
290
291   @param opts: the command line options selected by the user
292   @type args: list
293   @param args: should contain the command to be run and its arguments
294   @rtype: int
295   @return: the desired exit code
296
297   """
298   cl = GetClient()
299
300   command = " ".join(args)
301
302   nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
303
304   cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
305                                                     "master_node"])
306
307   srun = ssh.SshRunner(cluster_name=cluster_name)
308
309   # Make sure master node is at list end
310   if master_node in nodes:
311     nodes.remove(master_node)
312     nodes.append(master_node)
313
314   for name in nodes:
315     result = srun.Run(name, "root", command)
316     ToStdout("------------------------------------------------")
317     ToStdout("node: %s", name)
318     ToStdout("%s", result.output)
319     ToStdout("return code = %s", result.exit_code)
320
321   return 0
322
323
324 def VerifyCluster(opts, args):
325   """Verify integrity of cluster, performing various test on nodes.
326
327   @param opts: the command line options selected by the user
328   @type args: list
329   @param args: should be an empty list
330   @rtype: int
331   @return: the desired exit code
332
333   """
334   skip_checks = []
335   if opts.skip_nplusone_mem:
336     skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
337   op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
338   if SubmitOpCode(op):
339     return 0
340   else:
341     return 1
342
343
344 def VerifyDisks(opts, args):
345   """Verify integrity of cluster disks.
346
347   @param opts: the command line options selected by the user
348   @type args: list
349   @param args: should be an empty list
350   @rtype: int
351   @return: the desired exit code
352
353   """
354   op = opcodes.OpVerifyDisks()
355   result = SubmitOpCode(op)
356   if not isinstance(result, (list, tuple)) or len(result) != 4:
357     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
358
359   nodes, nlvm, instances, missing = result
360
361   if nodes:
362     ToStdout("Nodes unreachable or with bad data:")
363     for name in nodes:
364       ToStdout("\t%s", name)
365   retcode = constants.EXIT_SUCCESS
366
367   if nlvm:
368     for node, text in nlvm.iteritems():
369       ToStdout("Error on node %s: LVM error: %s",
370                node, utils.SafeEncode(text[-400:]))
371       retcode |= 1
372       ToStdout("You need to fix these nodes first before fixing instances")
373
374   if instances:
375     for iname in instances:
376       if iname in missing:
377         continue
378       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
379       try:
380         ToStdout("Activating disks for instance '%s'", iname)
381         SubmitOpCode(op)
382       except errors.GenericError, err:
383         nret, msg = FormatError(err)
384         retcode |= nret
385         ToStderr("Error activating disks for instance %s: %s", iname, msg)
386
387   if missing:
388     for iname, ival in missing.iteritems():
389       all_missing = utils.all(ival, lambda x: x[0] in nlvm)
390       if all_missing:
391         ToStdout("Instance %s cannot be verified as it lives on"
392                  " broken nodes", iname)
393       else:
394         ToStdout("Instance %s has missing logical volumes:", iname)
395         ival.sort()
396         for node, vol in ival:
397           if node in nlvm:
398             ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
399           else:
400             ToStdout("\t%s /dev/xenvg/%s", node, vol)
401     ToStdout("You need to run replace_disks for all the above"
402            " instances, if this message persist after fixing nodes.")
403     retcode |= 1
404
405   return retcode
406
407
408 @UsesRPC
409 def MasterFailover(opts, args):
410   """Failover the master node.
411
412   This command, when run on a non-master node, will cause the current
413   master to cease being master, and the non-master to become new
414   master.
415
416   @param opts: the command line options selected by the user
417   @type args: list
418   @param args: should be an empty list
419   @rtype: int
420   @return: the desired exit code
421
422   """
423   return bootstrap.MasterFailover()
424
425
426 def SearchTags(opts, args):
427   """Searches the tags on all the cluster.
428
429   @param opts: the command line options selected by the user
430   @type args: list
431   @param args: should contain only one element, the tag pattern
432   @rtype: int
433   @return: the desired exit code
434
435   """
436   op = opcodes.OpSearchTags(pattern=args[0])
437   result = SubmitOpCode(op)
438   if not result:
439     return 1
440   result = list(result)
441   result.sort()
442   for path, tag in result:
443     ToStdout("%s %s", path, tag)
444
445
446 def SetClusterParams(opts, args):
447   """Modify the cluster.
448
449   @param opts: the command line options selected by the user
450   @type args: list
451   @param args: should be an empty list
452   @rtype: int
453   @return: the desired exit code
454
455   """
456   if not (not opts.lvm_storage or opts.vg_name or
457           opts.enabled_hypervisors or opts.hvparams or
458           opts.beparams or opts.candidate_pool_size is not None):
459     ToStderr("Please give at least one of the parameters.")
460     return 1
461
462   vg_name = opts.vg_name
463   if not opts.lvm_storage and opts.vg_name:
464     ToStdout("Options --no-lvm-storage and --vg-name conflict.")
465     return 1
466
467   hvlist = opts.enabled_hypervisors
468   if hvlist is not None:
469     hvlist = hvlist.split(",")
470
471   # a list of (name, dict) we can pass directly to dict() (or [])
472   hvparams = dict(opts.hvparams)
473   for hv, hv_params in hvparams.iteritems():
474     utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
475
476   beparams = opts.beparams
477   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
478
479   op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
480                                   enabled_hypervisors=hvlist,
481                                   hvparams=hvparams,
482                                   beparams=beparams,
483                                   candidate_pool_size=opts.candidate_pool_size)
484   SubmitOpCode(op)
485   return 0
486
487
488 def QueueOps(opts, args):
489   """Queue operations.
490
491   @param opts: the command line options selected by the user
492   @type args: list
493   @param args: should contain only one element, the subcommand
494   @rtype: int
495   @return: the desired exit code
496
497   """
498   command = args[0]
499   client = GetClient()
500   if command in ("drain", "undrain"):
501     drain_flag = command == "drain"
502     client.SetQueueDrainFlag(drain_flag)
503   elif command == "info":
504     result = client.QueryConfigValues(["drain_flag"])
505     if result[0]:
506       val = "set"
507     else:
508       val = "unset"
509     ToStdout("The drain flag is %s" % val)
510   else:
511     raise errors.OpPrereqError("Command '%s' is not valid." % command)
512
513   return 0
514
515 # this is an option common to more than one command, so we declare
516 # it here and reuse it
517 node_option = make_option("-n", "--node", action="append", dest="nodes",
518                           help="Node to copy to (if not given, all nodes),"
519                                " can be given multiple times",
520                           metavar="<node>", default=[])
521
522 commands = {
523   'init': (InitCluster, ARGS_ONE,
524            [DEBUG_OPT,
525             make_option("-s", "--secondary-ip", dest="secondary_ip",
526                         help="Specify the secondary ip for this node;"
527                         " if given, the entire cluster must have secondary"
528                         " addresses",
529                         metavar="ADDRESS", default=None),
530             make_option("-m", "--mac-prefix", dest="mac_prefix",
531                         help="Specify the mac prefix for the instance IP"
532                         " addresses, in the format XX:XX:XX",
533                         metavar="PREFIX",
534                         default=constants.DEFAULT_MAC_PREFIX,),
535             make_option("-g", "--vg-name", dest="vg_name",
536                         help="Specify the volume group name "
537                         " (cluster-wide) for disk allocation [xenvg]",
538                         metavar="VG",
539                         default=None,),
540             make_option("-b", "--bridge", dest="def_bridge",
541                         help="Specify the default bridge name (cluster-wide)"
542                           " to connect the instances to [%s]" %
543                           constants.DEFAULT_BRIDGE,
544                         metavar="BRIDGE",
545                         default=constants.DEFAULT_BRIDGE,),
546             make_option("--master-netdev", dest="master_netdev",
547                         help="Specify the node interface (cluster-wide)"
548                           " on which the master IP address will be added "
549                           " [%s]" % constants.DEFAULT_BRIDGE,
550                         metavar="NETDEV",
551                         default=constants.DEFAULT_BRIDGE,),
552             make_option("--file-storage-dir", dest="file_storage_dir",
553                         help="Specify the default directory (cluster-wide)"
554                              " for storing the file-based disks [%s]" %
555                              constants.DEFAULT_FILE_STORAGE_DIR,
556                         metavar="DIR",
557                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
558             make_option("--no-lvm-storage", dest="lvm_storage",
559                         help="No support for lvm based instances"
560                              " (cluster-wide)",
561                         action="store_false", default=True,),
562             make_option("--enabled-hypervisors", dest="enabled_hypervisors",
563                         help="Comma-separated list of hypervisors",
564                         type="string", default=None),
565             make_option("-t", "--default-hypervisor",
566                         dest="default_hypervisor",
567                         help="Default hypervisor to use for instance creation",
568                         choices=list(constants.HYPER_TYPES),
569                         default=constants.DEFAULT_ENABLED_HYPERVISOR),
570             ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
571                        help="Hypervisor and hypervisor options, in the"
572                          " format"
573                        " hypervisor:option=value,option=value,...",
574                        default=[],
575                        action="append",
576                        type="identkeyval"),
577             keyval_option("-B", "--backend-parameters", dest="beparams",
578                           type="keyval", default={},
579                           help="Backend parameters"),
580             make_option("-C", "--candidate-pool-size",
581                         default=constants.MASTER_POOL_SIZE_DEFAULT,
582                         help="Set the candidate pool size",
583                         dest="candidate_pool_size", type="int"),
584             ],
585            "[opts...] <cluster_name>",
586            "Initialises a new cluster configuration"),
587   'destroy': (DestroyCluster, ARGS_NONE,
588               [DEBUG_OPT,
589                make_option("--yes-do-it", dest="yes_do_it",
590                            help="Destroy cluster",
591                            action="store_true"),
592               ],
593               "", "Destroy cluster"),
594   'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
595                "<new_name>",
596                "Renames the cluster"),
597   'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
598                   "",
599                   "Forces a push of the configuration file and ssconf files"
600                   " to the nodes in the cluster"),
601   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
602              make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
603                          help="Skip N+1 memory redundancy tests",
604                          action="store_true",
605                          default=False,),
606              ],
607              "", "Does a check on the cluster configuration"),
608   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
609                    "", "Does a check on the cluster disk status"),
610   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
611                      "", "Makes the current node the master"),
612   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
613               "", "Shows the cluster version"),
614   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
615                 "", "Shows the cluster master"),
616   'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
617                "[-n node...] <filename>",
618                "Copies a file to all (or only some) nodes"),
619   'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
620               "[-n node...] <command>",
621               "Runs a command on all (or only some) nodes"),
622   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
623                  "", "Show cluster configuration"),
624   'list-tags': (ListTags, ARGS_NONE,
625                 [DEBUG_OPT], "", "List the tags of the cluster"),
626   'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
627                "tag...", "Add tags to the cluster"),
628   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
629                   "tag...", "Remove tags from the cluster"),
630   'search-tags': (SearchTags, ARGS_ONE,
631                   [DEBUG_OPT], "", "Searches the tags on all objects on"
632                   " the cluster for a given pattern (regex)"),
633   'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
634             "drain|undrain|info", "Change queue properties"),
635   'modify': (SetClusterParams, ARGS_NONE,
636              [DEBUG_OPT,
637               make_option("-g", "--vg-name", dest="vg_name",
638                           help="Specify the volume group name "
639                           " (cluster-wide) for disk allocation "
640                           "and enable lvm based storage",
641                           metavar="VG",),
642               make_option("--no-lvm-storage", dest="lvm_storage",
643                           help="Disable support for lvm based instances"
644                                " (cluster-wide)",
645                           action="store_false", default=True,),
646               make_option("--enabled-hypervisors", dest="enabled_hypervisors",
647                           help="Comma-separated list of hypervisors",
648                           type="string", default=None),
649               ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
650                          help="Hypervisor and hypervisor options, in the"
651                          " format"
652                          " hypervisor:option=value,option=value,...",
653                          default=[],
654                          action="append",
655                          type="identkeyval"),
656               keyval_option("-B", "--backend-parameters", dest="beparams",
657                             type="keyval", default={},
658                             help="Backend parameters"),
659               make_option("-C", "--candidate-pool-size", default=None,
660                           help="Set the candidate pool size",
661                           dest="candidate_pool_size", type="int"),
662               ],
663              "[opts...]",
664              "Alters the parameters of the cluster"),
665   }
666
667 if __name__ == '__main__':
668   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))