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