Add NIC.CheckParameterSyntax
[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                         modify_etc_hosts=opts.modify_etc_hosts,
114                         )
115   return 0
116
117
118 @UsesRPC
119 def DestroyCluster(opts, args):
120   """Destroy the cluster.
121
122   @param opts: the command line options selected by the user
123   @type args: list
124   @param args: should be an empty list
125   @rtype: int
126   @return: the desired exit code
127
128   """
129   if not opts.yes_do_it:
130     ToStderr("Destroying a cluster is irreversible. If you really want"
131              " destroy this cluster, supply the --yes-do-it option.")
132     return 1
133
134   op = opcodes.OpDestroyCluster()
135   master = SubmitOpCode(op)
136   # if we reached this, the opcode didn't fail; we can proceed to
137   # shutdown all the daemons
138   bootstrap.FinalizeClusterDestroy(master)
139   return 0
140
141
142 def RenameCluster(opts, args):
143   """Rename the cluster.
144
145   @param opts: the command line options selected by the user
146   @type args: list
147   @param args: should contain only one element, the new cluster name
148   @rtype: int
149   @return: the desired exit code
150
151   """
152   name = args[0]
153   if not opts.force:
154     usertext = ("This will rename the cluster to '%s'. If you are connected"
155                 " over the network to the cluster name, the operation is very"
156                 " dangerous as the IP address will be removed from the node"
157                 " and the change may not go through. Continue?") % name
158     if not AskUser(usertext):
159       return 1
160
161   op = opcodes.OpRenameCluster(name=name)
162   SubmitOpCode(op)
163   return 0
164
165
166 def RedistributeConfig(opts, args):
167   """Forces push of the cluster configuration.
168
169   @param opts: the command line options selected by the user
170   @type args: list
171   @param args: empty list
172   @rtype: int
173   @return: the desired exit code
174
175   """
176   op = opcodes.OpRedistributeConfig()
177   SubmitOrSend(op, opts)
178   return 0
179
180
181 def ShowClusterVersion(opts, args):
182   """Write version of ganeti software to the standard output.
183
184   @param opts: the command line options selected by the user
185   @type args: list
186   @param args: should be an empty list
187   @rtype: int
188   @return: the desired exit code
189
190   """
191   cl = GetClient()
192   result = cl.QueryClusterInfo()
193   ToStdout("Software version: %s", result["software_version"])
194   ToStdout("Internode protocol: %s", result["protocol_version"])
195   ToStdout("Configuration format: %s", result["config_version"])
196   ToStdout("OS api version: %s", result["os_api_version"])
197   ToStdout("Export interface: %s", result["export_version"])
198   return 0
199
200
201 def ShowClusterMaster(opts, args):
202   """Write name of master node to the standard output.
203
204   @param opts: the command line options selected by the user
205   @type args: list
206   @param args: should be an empty list
207   @rtype: int
208   @return: the desired exit code
209
210   """
211   master = bootstrap.GetMaster()
212   ToStdout(master)
213   return 0
214
215
216 def ShowClusterConfig(opts, args):
217   """Shows cluster information.
218
219   @param opts: the command line options selected by the user
220   @type args: list
221   @param args: should be an empty list
222   @rtype: int
223   @return: the desired exit code
224
225   """
226   cl = GetClient()
227   result = cl.QueryClusterInfo()
228
229   ToStdout("Cluster name: %s", result["name"])
230
231   ToStdout("Master node: %s", result["master"])
232
233   ToStdout("Architecture (this node): %s (%s)",
234            result["architecture"][0], result["architecture"][1])
235
236   ToStdout("Default hypervisor: %s", result["default_hypervisor"])
237   ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
238
239   ToStdout("Hypervisor parameters:")
240   for hv_name, hv_dict in result["hvparams"].items():
241     ToStdout("  - %s:", hv_name)
242     for item, val in hv_dict.iteritems():
243       ToStdout("      %s: %s", item, val)
244
245   ToStdout("Cluster parameters:")
246   ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
247   ToStdout("  - master netdev: %s", result["master_netdev"])
248   ToStdout("  - default bridge: %s", result["default_bridge"])
249   ToStdout("  - lvm volume group: %s", result["volume_group_name"])
250   ToStdout("  - file storage path: %s", result["file_storage_dir"])
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   elif not opts.lvm_storage:
472     vg_name = ''
473
474   hvlist = opts.enabled_hypervisors
475   if hvlist is not None:
476     hvlist = hvlist.split(",")
477
478   # a list of (name, dict) we can pass directly to dict() (or [])
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=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   else:
518     raise errors.OpPrereqError("Command '%s' is not valid." % command)
519
520   return 0
521
522 # this is an option common to more than one command, so we declare
523 # it here and reuse it
524 node_option = make_option("-n", "--node", action="append", dest="nodes",
525                           help="Node to copy to (if not given, all nodes),"
526                                " can be given multiple times",
527                           metavar="<node>", default=[])
528
529 commands = {
530   'init': (InitCluster, ARGS_ONE,
531            [DEBUG_OPT,
532             make_option("-s", "--secondary-ip", dest="secondary_ip",
533                         help="Specify the secondary ip for this node;"
534                         " if given, the entire cluster must have secondary"
535                         " addresses",
536                         metavar="ADDRESS", default=None),
537             make_option("-m", "--mac-prefix", dest="mac_prefix",
538                         help="Specify the mac prefix for the instance IP"
539                         " addresses, in the format XX:XX:XX",
540                         metavar="PREFIX",
541                         default=constants.DEFAULT_MAC_PREFIX,),
542             make_option("-g", "--vg-name", dest="vg_name",
543                         help="Specify the volume group name "
544                         " (cluster-wide) for disk allocation [xenvg]",
545                         metavar="VG",
546                         default=None,),
547             make_option("-b", "--bridge", dest="def_bridge",
548                         help="Specify the default bridge name (cluster-wide)"
549                           " to connect the instances to [%s]" %
550                           constants.DEFAULT_BRIDGE,
551                         metavar="BRIDGE",
552                         default=constants.DEFAULT_BRIDGE,),
553             make_option("--master-netdev", dest="master_netdev",
554                         help="Specify the node interface (cluster-wide)"
555                           " on which the master IP address will be added "
556                           " [%s]" % constants.DEFAULT_BRIDGE,
557                         metavar="NETDEV",
558                         default=constants.DEFAULT_BRIDGE,),
559             make_option("--file-storage-dir", dest="file_storage_dir",
560                         help="Specify the default directory (cluster-wide)"
561                              " for storing the file-based disks [%s]" %
562                              constants.DEFAULT_FILE_STORAGE_DIR,
563                         metavar="DIR",
564                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
565             make_option("--no-lvm-storage", dest="lvm_storage",
566                         help="No support for lvm based instances"
567                              " (cluster-wide)",
568                         action="store_false", default=True,),
569             make_option("--no-etc-hosts", dest="modify_etc_hosts",
570                         help="Don't modify /etc/hosts"
571                              " (cluster-wide)",
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             make_option("-t", "--default-hypervisor",
577                         dest="default_hypervisor",
578                         help="Default hypervisor to use for instance creation",
579                         choices=list(constants.HYPER_TYPES),
580                         default=constants.DEFAULT_ENABLED_HYPERVISOR),
581             ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
582                        help="Hypervisor and hypervisor options, in the"
583                          " format"
584                        " hypervisor:option=value,option=value,...",
585                        default=[],
586                        action="append",
587                        type="identkeyval"),
588             keyval_option("-B", "--backend-parameters", dest="beparams",
589                           type="keyval", default={},
590                           help="Backend parameters"),
591             make_option("-C", "--candidate-pool-size",
592                         default=constants.MASTER_POOL_SIZE_DEFAULT,
593                         help="Set the candidate pool size",
594                         dest="candidate_pool_size", type="int"),
595             ],
596            "[opts...] <cluster_name>",
597            "Initialises a new cluster configuration"),
598   'destroy': (DestroyCluster, ARGS_NONE,
599               [DEBUG_OPT,
600                make_option("--yes-do-it", dest="yes_do_it",
601                            help="Destroy cluster",
602                            action="store_true"),
603               ],
604               "", "Destroy cluster"),
605   'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
606                "<new_name>",
607                "Renames the cluster"),
608   'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
609                   "",
610                   "Forces a push of the configuration file and ssconf files"
611                   " to the nodes in the cluster"),
612   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
613              make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
614                          help="Skip N+1 memory redundancy tests",
615                          action="store_true",
616                          default=False,),
617              ],
618              "", "Does a check on the cluster configuration"),
619   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
620                    "", "Does a check on the cluster disk status"),
621   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
622                      "", "Makes the current node the master"),
623   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
624               "", "Shows the cluster version"),
625   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
626                 "", "Shows the cluster master"),
627   'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
628                "[-n node...] <filename>",
629                "Copies a file to all (or only some) nodes"),
630   'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
631               "[-n node...] <command>",
632               "Runs a command on all (or only some) nodes"),
633   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
634                  "", "Show cluster configuration"),
635   'list-tags': (ListTags, ARGS_NONE,
636                 [DEBUG_OPT], "", "List the tags of the cluster"),
637   'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
638                "tag...", "Add tags to the cluster"),
639   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
640                   "tag...", "Remove tags from the cluster"),
641   'search-tags': (SearchTags, ARGS_ONE,
642                   [DEBUG_OPT], "", "Searches the tags on all objects on"
643                   " the cluster for a given pattern (regex)"),
644   'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
645             "drain|undrain|info", "Change queue properties"),
646   'modify': (SetClusterParams, ARGS_NONE,
647              [DEBUG_OPT,
648               make_option("-g", "--vg-name", dest="vg_name",
649                           help="Specify the volume group name "
650                           " (cluster-wide) for disk allocation "
651                           "and enable lvm based storage",
652                           metavar="VG",),
653               make_option("--no-lvm-storage", dest="lvm_storage",
654                           help="Disable support for lvm based instances"
655                                " (cluster-wide)",
656                           action="store_false", default=True,),
657               make_option("--enabled-hypervisors", dest="enabled_hypervisors",
658                           help="Comma-separated list of hypervisors",
659                           type="string", default=None),
660               ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
661                          help="Hypervisor and hypervisor options, in the"
662                          " format"
663                          " hypervisor:option=value,option=value,...",
664                          default=[],
665                          action="append",
666                          type="identkeyval"),
667               keyval_option("-B", "--backend-parameters", dest="beparams",
668                             type="keyval", default={},
669                             help="Backend parameters"),
670               make_option("-C", "--candidate-pool-size", default=None,
671                           help="Set the candidate pool size",
672                           dest="candidate_pool_size", type="int"),
673               ],
674              "[opts...]",
675              "Alters the parameters of the cluster"),
676   }
677
678 if __name__ == '__main__':
679   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))