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