Rework master startup/shutdown/failover
[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   bootstrap.InitCluster(cluster_name=args[0],
54                         secondary_ip=opts.secondary_ip,
55                         hypervisor_type=opts.hypervisor_type,
56                         vg_name=vg_name,
57                         mac_prefix=opts.mac_prefix,
58                         def_bridge=opts.def_bridge,
59                         master_netdev=opts.master_netdev,
60                         file_storage_dir=opts.file_storage_dir)
61   return 0
62
63
64 def DestroyCluster(opts, args):
65   """Destroy the cluster.
66
67   Args:
68     opts - class with options as members
69
70   """
71   if not opts.yes_do_it:
72     print ("Destroying a cluster is irreversibly. If you really want destroy"
73            " this cluster, supply the --yes-do-it option.")
74     return 1
75
76   op = opcodes.OpDestroyCluster()
77   SubmitOpCode(op)
78   return 0
79
80
81 def RenameCluster(opts, args):
82   """Rename the cluster.
83
84   Args:
85     opts - class with options as members, we use force only
86     args - list of arguments, expected to be [new_name]
87
88   """
89   name = args[0]
90   if not opts.force:
91     usertext = ("This will rename the cluster to '%s'. If you are connected"
92                 " over the network to the cluster name, the operation is very"
93                 " dangerous as the IP address will be removed from the node"
94                 " and the change may not go through. Continue?") % name
95     if not AskUser(usertext):
96       return 1
97
98   op = opcodes.OpRenameCluster(name=name)
99   SubmitOpCode(op)
100   return 0
101
102
103 def ShowClusterVersion(opts, args):
104   """Write version of ganeti software to the standard output.
105
106   Args:
107     opts - class with options as members
108
109   """
110   op = opcodes.OpQueryClusterInfo()
111   result = SubmitOpCode(op)
112   print ("Software version: %s" % result["software_version"])
113   print ("Internode protocol: %s" % result["protocol_version"])
114   print ("Configuration format: %s" % result["config_version"])
115   print ("OS api version: %s" % result["os_api_version"])
116   print ("Export interface: %s" % result["export_version"])
117   return 0
118
119
120 def ShowClusterMaster(opts, args):
121   """Write name of master node to the standard output.
122
123   Args:
124     opts - class with options as members
125
126   """
127   op = opcodes.OpQueryClusterInfo()
128   result = SubmitOpCode(op)
129   print (result["master"])
130   return 0
131
132
133 def ShowClusterConfig(opts, args):
134   """Shows cluster information.
135
136   """
137   op = opcodes.OpQueryClusterInfo()
138   result = SubmitOpCode(op)
139
140   print ("Cluster name: %s" % result["name"])
141
142   print ("Master node: %s" % result["master"])
143
144   print ("Architecture (this node): %s (%s)" %
145          (result["architecture"][0], result["architecture"][1]))
146
147   print ("Cluster hypervisor: %s" % result["hypervisor_type"])
148
149   return 0
150
151
152 def ClusterCopyFile(opts, args):
153   """Copy a file from master to some nodes.
154
155   Args:
156     opts - class with options as members
157     args - list containing a single element, the file name
158   Opts used:
159     nodes - list containing the name of target nodes; if empty, all nodes
160
161   """
162   filename = args[0]
163   if not os.path.exists(filename):
164     raise errors.OpPrereqError("No such filename '%s'" % filename)
165
166   myname = utils.HostInfo().name
167
168   op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
169   results = [row[0] for row in SubmitOpCode(op) if row[0] != myname]
170   srun = ssh.SshRunner()
171   for node in results:
172     if not srun.CopyFileToNode(node, filename):
173       print >> sys.stderr, ("Copy of file %s to node %s failed" %
174                             (filename, node))
175
176   return 0
177
178
179 def RunClusterCommand(opts, args):
180   """Run a command on some nodes.
181
182   Args:
183     opts - class with options as members
184     args - the command list as a list
185   Opts used:
186     nodes: list containing the name of target nodes; if empty, all nodes
187
188   """
189   command = " ".join(args)
190   op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
191   nodes = [row[0] for row in SubmitOpCode(op)]
192
193   sstore = ssconf.SimpleStore()
194   master_node = sstore.GetMasterNode()
195   srun = ssh.SshRunner(sstore=sstore)
196
197   if master_node in nodes:
198     nodes.remove(master_node)
199     nodes.append(master_node)
200
201   for name in nodes:
202     result = srun.Run(name, "root", command)
203     print ("------------------------------------------------")
204     print ("node: %s" % name)
205     print ("%s" % result.output)
206     print ("return code = %s" % result.exit_code)
207
208   return 0
209
210
211 def VerifyCluster(opts, args):
212   """Verify integrity of cluster, performing various test on nodes.
213
214   Args:
215     opts - class with options as members
216
217   """
218   skip_checks = []
219   if opts.skip_nplusone_mem:
220     skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
221   op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
222   result = SubmitOpCode(op)
223   return result
224
225
226 def VerifyDisks(opts, args):
227   """Verify integrity of cluster disks.
228
229   Args:
230     opts - class with options as members
231
232   """
233   op = opcodes.OpVerifyDisks()
234   result = SubmitOpCode(op)
235   if not isinstance(result, tuple) or len(result) != 4:
236     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
237
238   nodes, nlvm, instances, missing = result
239
240   if nodes:
241     print "Nodes unreachable or with bad data:"
242     for name in nodes:
243       print "\t%s" % name
244   retcode = constants.EXIT_SUCCESS
245
246   if nlvm:
247     for node, text in nlvm.iteritems():
248       print ("Error on node %s: LVM error: %s" %
249              (node, text[-400:].encode('string_escape')))
250       retcode |= 1
251       print "You need to fix these nodes first before fixing instances"
252
253   if instances:
254     for iname in instances:
255       if iname in missing:
256         continue
257       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
258       try:
259         print "Activating disks for instance '%s'" % iname
260         SubmitOpCode(op)
261       except errors.GenericError, err:
262         nret, msg = FormatError(err)
263         retcode |= nret
264         print >> sys.stderr, ("Error activating disks for instance %s: %s" %
265                               (iname, msg))
266
267   if missing:
268     for iname, ival in missing.iteritems():
269       all_missing = utils.all(ival, lambda x: x[0] in nlvm)
270       if all_missing:
271         print ("Instance %s cannot be verified as it lives on"
272                " broken nodes" % iname)
273       else:
274         print "Instance %s has missing logical volumes:" % iname
275         ival.sort()
276         for node, vol in ival:
277           if node in nlvm:
278             print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
279           else:
280             print ("\t%s /dev/xenvg/%s" % (node, vol))
281     print ("You need to run replace_disks for all the above"
282            " instances, if this message persist after fixing nodes.")
283     retcode |= 1
284
285   return retcode
286
287
288 def MasterFailover(opts, args):
289   """Failover the master node.
290
291   This command, when run on a non-master node, will cause the current
292   master to cease being master, and the non-master to become new
293   master.
294
295   """
296   return bootstrap.MasterFailover()
297
298
299 def SearchTags(opts, args):
300   """Searches the tags on all the cluster.
301
302   """
303   op = opcodes.OpSearchTags(pattern=args[0])
304   result = SubmitOpCode(op)
305   if not result:
306     return 1
307   result = list(result)
308   result.sort()
309   for path, tag in result:
310     print "%s %s" % (path, tag)
311
312
313 def SetClusterParams(opts, args):
314   """Modify the cluster.
315
316   Args:
317     opts - class with options as members
318
319   """
320   if not (not opts.lvm_storage or opts.vg_name):
321     print "Please give at least one of the parameters."
322     return 1
323
324   vg_name = opts.vg_name
325   if not opts.lvm_storage and opts.vg_name:
326     print ("Options --no-lvm-storage and --vg-name conflict.")
327     return 1
328
329   op = opcodes.OpSetClusterParams(vg_name=opts.vg_name)
330   SubmitOpCode(op)
331   return 0
332
333
334 # this is an option common to more than one command, so we declare
335 # it here and reuse it
336 node_option = make_option("-n", "--node", action="append", dest="nodes",
337                           help="Node to copy to (if not given, all nodes),"
338                                " can be given multiple times",
339                           metavar="<node>", default=[])
340
341 commands = {
342   'init': (InitCluster, ARGS_ONE,
343            [DEBUG_OPT,
344             make_option("-s", "--secondary-ip", dest="secondary_ip",
345                         help="Specify the secondary ip for this node;"
346                         " if given, the entire cluster must have secondary"
347                         " addresses",
348                         metavar="ADDRESS", default=None),
349             make_option("-t", "--hypervisor-type", dest="hypervisor_type",
350                         help="Specify the hypervisor type "
351                         "(xen-3.0, fake, xen-hvm-3.1)",
352                         metavar="TYPE", choices=["xen-3.0",
353                                                  "fake",
354                                                  "xen-hvm-3.1"],
355                         default="xen-3.0",),
356             make_option("-m", "--mac-prefix", dest="mac_prefix",
357                         help="Specify the mac prefix for the instance IP"
358                         " addresses, in the format XX:XX:XX",
359                         metavar="PREFIX",
360                         default="aa:00:00",),
361             make_option("-g", "--vg-name", dest="vg_name",
362                         help="Specify the volume group name "
363                         " (cluster-wide) for disk allocation [xenvg]",
364                         metavar="VG",
365                         default=None,),
366             make_option("-b", "--bridge", dest="def_bridge",
367                         help="Specify the default bridge name (cluster-wide)"
368                           " to connect the instances to [%s]" %
369                           constants.DEFAULT_BRIDGE,
370                         metavar="BRIDGE",
371                         default=constants.DEFAULT_BRIDGE,),
372             make_option("--master-netdev", dest="master_netdev",
373                         help="Specify the node interface (cluster-wide)"
374                           " on which the master IP address will be added "
375                           " [%s]" % constants.DEFAULT_BRIDGE,
376                         metavar="NETDEV",
377                         default=constants.DEFAULT_BRIDGE,),
378             make_option("--file-storage-dir", dest="file_storage_dir",
379                         help="Specify the default directory (cluster-wide)"
380                              " for storing the file-based disks [%s]" %
381                              constants.DEFAULT_FILE_STORAGE_DIR,
382                         metavar="DIR",
383                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
384             make_option("--no-lvm-storage", dest="lvm_storage",
385                         help="No support for lvm based instances"
386                              " (cluster-wide)",
387                         action="store_false", default=True,),
388             ],
389            "[opts...] <cluster_name>",
390            "Initialises a new cluster configuration"),
391   'destroy': (DestroyCluster, ARGS_NONE,
392               [DEBUG_OPT,
393                make_option("--yes-do-it", dest="yes_do_it",
394                            help="Destroy cluster",
395                            action="store_true"),
396               ],
397               "", "Destroy cluster"),
398   'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
399                "<new_name>",
400                "Renames the cluster"),
401   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
402              make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
403                          help="Skip N+1 memory redundancy tests",
404                          action="store_true",
405                          default=False,),
406              ],
407              "", "Does a check on the cluster configuration"),
408   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
409                    "", "Does a check on the cluster disk status"),
410   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
411                      "", "Makes the current node the master"),
412   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
413               "", "Shows the cluster version"),
414   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
415                 "", "Shows the cluster master"),
416   'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
417                "[-n node...] <filename>",
418                "Copies a file to all (or only some) nodes"),
419   'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
420               "[-n node...] <command>",
421               "Runs a command on all (or only some) nodes"),
422   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
423                  "", "Show cluster configuration"),
424   'list-tags': (ListTags, ARGS_NONE,
425                 [DEBUG_OPT], "", "List the tags of the cluster"),
426   'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
427                "tag...", "Add tags to the cluster"),
428   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
429                   "tag...", "Remove tags from the cluster"),
430   'search-tags': (SearchTags, ARGS_ONE,
431                   [DEBUG_OPT], "", "Searches the tags on all objects on"
432                   " the cluster for a given pattern (regex)"),
433   'modify': (SetClusterParams, ARGS_NONE,
434              [DEBUG_OPT,
435               make_option("-g", "--vg-name", dest="vg_name",
436                           help="Specify the volume group name "
437                           " (cluster-wide) for disk allocation "
438                           "and enable lvm based storage",
439                           metavar="VG",),
440               make_option("--no-lvm-storage", dest="lvm_storage",
441                           help="Disable support for lvm based instances"
442                                " (cluster-wide)",
443                           action="store_false", default=True,),
444               ],
445              "[opts...]",
446              "Alters the parameters of the cluster"),
447   }
448
449 if __name__ == '__main__':
450   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))