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