Allow setting NIC parameters at gnt-cluster init
[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.candidate_pool_size is not None):
461     ToStderr("Please give at least one of the parameters.")
462     return 1
463
464   vg_name = opts.vg_name
465   if not opts.lvm_storage and opts.vg_name:
466     ToStdout("Options --no-lvm-storage and --vg-name conflict.")
467     return 1
468   elif not opts.lvm_storage:
469     vg_name = ''
470
471   hvlist = opts.enabled_hypervisors
472   if hvlist is not None:
473     hvlist = hvlist.split(",")
474
475   # a list of (name, dict) we can pass directly to dict() (or [])
476   hvparams = dict(opts.hvparams)
477   for hv, hv_params in hvparams.iteritems():
478     utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
479
480   beparams = opts.beparams
481   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
482
483   op = opcodes.OpSetClusterParams(vg_name=vg_name,
484                                   enabled_hypervisors=hvlist,
485                                   hvparams=hvparams,
486                                   beparams=beparams,
487                                   candidate_pool_size=opts.candidate_pool_size)
488   SubmitOpCode(op)
489   return 0
490
491
492 def QueueOps(opts, args):
493   """Queue operations.
494
495   @param opts: the command line options selected by the user
496   @type args: list
497   @param args: should contain only one element, the subcommand
498   @rtype: int
499   @return: the desired exit code
500
501   """
502   command = args[0]
503   client = GetClient()
504   if command in ("drain", "undrain"):
505     drain_flag = command == "drain"
506     client.SetQueueDrainFlag(drain_flag)
507   elif command == "info":
508     result = client.QueryConfigValues(["drain_flag"])
509     if result[0]:
510       val = "set"
511     else:
512       val = "unset"
513     ToStdout("The drain flag is %s" % val)
514   else:
515     raise errors.OpPrereqError("Command '%s' is not valid." % command)
516
517   return 0
518
519 # this is an option common to more than one command, so we declare
520 # it here and reuse it
521 node_option = make_option("-n", "--node", action="append", dest="nodes",
522                           help="Node to copy to (if not given, all nodes),"
523                                " can be given multiple times",
524                           metavar="<node>", default=[])
525
526 commands = {
527   'init': (InitCluster, ARGS_ONE,
528            [DEBUG_OPT,
529             make_option("-s", "--secondary-ip", dest="secondary_ip",
530                         help="Specify the secondary ip for this node;"
531                         " if given, the entire cluster must have secondary"
532                         " addresses",
533                         metavar="ADDRESS", default=None),
534             make_option("-m", "--mac-prefix", dest="mac_prefix",
535                         help="Specify the mac prefix for the instance IP"
536                         " addresses, in the format XX:XX:XX",
537                         metavar="PREFIX",
538                         default=constants.DEFAULT_MAC_PREFIX,),
539             make_option("-g", "--vg-name", dest="vg_name",
540                         help="Specify the volume group name "
541                         " (cluster-wide) for disk allocation [xenvg]",
542                         metavar="VG",
543                         default=None,),
544             make_option("-b", "--bridge", dest="def_bridge",
545                         help="Specify the default bridge name (cluster-wide)"
546                           " to connect the instances to [%s]" %
547                           constants.DEFAULT_BRIDGE,
548                         metavar="BRIDGE",
549                         default=constants.DEFAULT_BRIDGE,),
550             make_option("--master-netdev", dest="master_netdev",
551                         help="Specify the node interface (cluster-wide)"
552                           " on which the master IP address will be added "
553                           " [%s]" % constants.DEFAULT_BRIDGE,
554                         metavar="NETDEV",
555                         default=constants.DEFAULT_BRIDGE,),
556             make_option("--file-storage-dir", dest="file_storage_dir",
557                         help="Specify the default directory (cluster-wide)"
558                              " for storing the file-based disks [%s]" %
559                              constants.DEFAULT_FILE_STORAGE_DIR,
560                         metavar="DIR",
561                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
562             make_option("--no-lvm-storage", dest="lvm_storage",
563                         help="No support for lvm based instances"
564                              " (cluster-wide)",
565                         action="store_false", default=True,),
566             make_option("--no-etc-hosts", dest="modify_etc_hosts",
567                         help="Don't modify /etc/hosts"
568                              " (cluster-wide)",
569                         action="store_false", default=True,),
570             make_option("--enabled-hypervisors", dest="enabled_hypervisors",
571                         help="Comma-separated list of hypervisors",
572                         type="string", default=None),
573             make_option("-t", "--default-hypervisor",
574                         dest="default_hypervisor",
575                         help="Default hypervisor to use for instance creation",
576                         choices=list(constants.HYPER_TYPES),
577                         default=constants.DEFAULT_ENABLED_HYPERVISOR),
578             ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
579                        help="Hypervisor and hypervisor options, in the"
580                          " format"
581                        " hypervisor:option=value,option=value,...",
582                        default=[],
583                        action="append",
584                        type="identkeyval"),
585             keyval_option("-B", "--backend-parameters", dest="beparams",
586                           type="keyval", default={},
587                           help="Backend parameters"),
588             keyval_option("-N", "--nic-parameters", dest="nicparams",
589                           type="keyval", default={},
590                           help="NIC 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}))