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