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