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