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