Add option to change default file_storage_dir
[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
26 from ganeti.cli import *
27 from ganeti import opcodes
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import utils
31
32
33 def InitCluster(opts, args):
34   """Initialize the cluster.
35
36   Args:
37     opts - class with options as members
38     args - list of arguments, expected to be [clustername]
39
40   """
41   op = opcodes.OpInitCluster(cluster_name=args[0],
42                              secondary_ip=opts.secondary_ip,
43                              hypervisor_type=opts.hypervisor_type,
44                              vg_name=opts.vg_name,
45                              mac_prefix=opts.mac_prefix,
46                              def_bridge=opts.def_bridge,
47                              master_netdev=opts.master_netdev,
48                              file_storage_dir=opts.file_storage_dir)
49   SubmitOpCode(op)
50   return 0
51
52
53 def DestroyCluster(opts, args):
54   """Destroy the cluster.
55
56   Args:
57     opts - class with options as members
58
59   """
60   if not opts.yes_do_it:
61     print ("Destroying a cluster is irreversibly. If you really want destroy"
62            " this cluster, supply the --yes-do-it option.")
63     return 1
64
65   op = opcodes.OpDestroyCluster()
66   SubmitOpCode(op)
67   return 0
68
69
70 def RenameCluster(opts, args):
71   """Rename the cluster.
72
73   Args:
74     opts - class with options as members, we use force only
75     args - list of arguments, expected to be [new_name]
76
77   """
78   name = args[0]
79   if not opts.force:
80     usertext = ("This will rename the cluster to '%s'. If you are connected"
81                 " over the network to the cluster name, the operation is very"
82                 " dangerous as the IP address will be removed from the node"
83                 " and the change may not go through. Continue?") % name
84     if not AskUser(usertext):
85       return 1
86
87   op = opcodes.OpRenameCluster(name=name)
88   SubmitOpCode(op)
89   return 0
90
91
92 def ShowClusterVersion(opts, args):
93   """Write version of ganeti software to the standard output.
94
95   Args:
96     opts - class with options as members
97
98   """
99   op = opcodes.OpQueryClusterInfo()
100   result = SubmitOpCode(op)
101   print ("Software version: %s" % result["software_version"])
102   print ("Internode protocol: %s" % result["protocol_version"])
103   print ("Configuration format: %s" % result["config_version"])
104   print ("OS api version: %s" % result["os_api_version"])
105   print ("Export interface: %s" % result["export_version"])
106   return 0
107
108
109 def ShowClusterMaster(opts, args):
110   """Write name of master node to the standard output.
111
112   Args:
113     opts - class with options as members
114
115   """
116   op = opcodes.OpQueryClusterInfo()
117   result = SubmitOpCode(op)
118   print (result["master"])
119   return 0
120
121
122 def ShowClusterConfig(opts, args):
123   """Shows cluster information.
124
125   """
126   op = opcodes.OpQueryClusterInfo()
127   result = SubmitOpCode(op)
128
129   print ("Cluster name: %s" % result["name"])
130
131   print ("Master node: %s" % result["master"])
132
133   print ("Architecture (this node): %s (%s)" %
134          (result["architecture"][0], result["architecture"][1]))
135
136   return 0
137
138
139 def ClusterCopyFile(opts, args):
140   """Copy a file from master to some nodes.
141
142   Args:
143     opts - class with options as members
144     args - list containing a single element, the file name
145   Opts used:
146     nodes - list containing the name of target nodes; if empty, all nodes
147
148   """
149   op = opcodes.OpClusterCopyFile(filename=args[0], nodes=opts.nodes)
150   SubmitOpCode(op)
151   return 0
152
153
154 def RunClusterCommand(opts, args):
155   """Run a command on some nodes.
156
157   Args:
158     opts - class with options as members
159     args - the command list as a list
160   Opts used:
161     nodes: list containing the name of target nodes; if empty, all nodes
162
163   """
164   command = " ".join(args)
165   nodes = opts.nodes
166   op = opcodes.OpRunClusterCommand(command=command, nodes=nodes)
167   result = SubmitOpCode(op)
168   for node, output, exit_code in result:
169     print ("------------------------------------------------")
170     print ("node: %s" % node)
171     print ("%s" % output)
172     print ("return code = %s" % exit_code)
173
174
175 def VerifyCluster(opts, args):
176   """Verify integrity of cluster, performing various test on nodes.
177
178   Args:
179     opts - class with options as members
180
181   """
182   op = opcodes.OpVerifyCluster()
183   result = SubmitOpCode(op)
184   return result
185
186
187 def VerifyDisks(opts, args):
188   """Verify integrity of cluster disks.
189
190   Args:
191     opts - class with options as members
192
193   """
194   op = opcodes.OpVerifyDisks()
195   result = SubmitOpCode(op)
196   if not isinstance(result, tuple) or len(result) != 4:
197     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
198
199   nodes, nlvm, instances, missing = result
200
201   if nodes:
202     print "Nodes unreachable or with bad data:"
203     for name in nodes:
204       print "\t%s" % name
205   retcode = constants.EXIT_SUCCESS
206
207   if nlvm:
208     for node, text in nlvm.iteritems():
209       print ("Error on node %s: LVM error: %s" %
210              (node, text[-400:].encode('string_escape')))
211       retcode |= 1
212       print "You need to fix these nodes first before fixing instances"
213
214   if instances:
215     for iname in instances:
216       if iname in missing:
217         continue
218       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
219       try:
220         print "Activating disks for instance '%s'" % iname
221         SubmitOpCode(op)
222       except errors.GenericError, err:
223         nret, msg = FormatError(err)
224         retcode |= nret
225         print >> sys.stderr, ("Error activating disks for instance %s: %s" %
226                               (iname, msg))
227
228   if missing:
229     for iname, ival in missing.iteritems():
230       all_missing = utils.all(ival, lambda x: x[0] in nlvm)
231       if all_missing:
232         print ("Instance %s cannot be verified as it lives on"
233                " broken nodes" % iname)
234       else:
235         print "Instance %s has missing logical volumes:" % iname
236         ival.sort()
237         for node, vol in ival:
238           if node in nlvm:
239             print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
240           else:
241             print ("\t%s /dev/xenvg/%s" % (node, vol))
242     print ("You need to run replace_disks for all the above"
243            " instances, if this message persist after fixing nodes.")
244     retcode |= 1
245
246   return retcode
247
248
249 def MasterFailover(opts, args):
250   """Failover the master node.
251
252   This command, when run on a non-master node, will cause the current
253   master to cease being master, and the non-master to become new
254   master.
255
256   """
257   op = opcodes.OpMasterFailover()
258   SubmitOpCode(op)
259
260
261 def SearchTags(opts, args):
262   """Searches the tags on all the cluster.
263
264   """
265   op = opcodes.OpSearchTags(pattern=args[0])
266   result = SubmitOpCode(op)
267   if not result:
268     return 1
269   result = list(result)
270   result.sort()
271   for path, tag in result:
272     print "%s %s" % (path, tag)
273
274
275 # this is an option common to more than one command, so we declare
276 # it here and reuse it
277 node_option = make_option("-n", "--node", action="append", dest="nodes",
278                           help="Node to copy to (if not given, all nodes),"
279                                " can be given multiple times",
280                           metavar="<node>", default=[])
281
282 commands = {
283   'init': (InitCluster, ARGS_ONE,
284            [DEBUG_OPT,
285             make_option("-s", "--secondary-ip", dest="secondary_ip",
286                         help="Specify the secondary ip for this node;"
287                         " if given, the entire cluster must have secondary"
288                         " addresses",
289                         metavar="ADDRESS", default=None),
290             make_option("-t", "--hypervisor-type", dest="hypervisor_type",
291                         help="Specify the hypervisor type "
292                         "(xen-3.0, fake, xen-hvm-3.1)",
293                         metavar="TYPE", choices=["xen-3.0",
294                                                  "fake",
295                                                  "xen-hvm-3.1"],
296                         default="xen-3.0",),
297             make_option("-m", "--mac-prefix", dest="mac_prefix",
298                         help="Specify the mac prefix for the instance IP"
299                         " addresses, in the format XX:XX:XX",
300                         metavar="PREFIX",
301                         default="aa:00:00",),
302             make_option("-g", "--vg-name", dest="vg_name",
303                         help="Specify the volume group name "
304                         " (cluster-wide) for disk allocation [xenvg]",
305                         metavar="VG",
306                         default="xenvg",),
307             make_option("-b", "--bridge", dest="def_bridge",
308                         help="Specify the default bridge name (cluster-wide)"
309                           " to connect the instances to [%s]" %
310                           constants.DEFAULT_BRIDGE,
311                         metavar="BRIDGE",
312                         default=constants.DEFAULT_BRIDGE,),
313             make_option("--master-netdev", dest="master_netdev",
314                         help="Specify the node interface (cluster-wide)"
315                           " on which the master IP address will be added "
316                           " [%s]" % constants.DEFAULT_BRIDGE,
317                         metavar="NETDEV",
318                         default=constants.DEFAULT_BRIDGE,),
319             make_option("--file-storage-dir", dest="file_storage_dir",
320                         help="Specify the default directory (cluster-wide)"
321                              " for storing the file-based disks [%s]" %
322                              constants.DEFAULT_FILE_STORAGE_DIR,
323                         metavar="DIR",
324                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
325             ],
326            "[opts...] <cluster_name>",
327            "Initialises a new cluster configuration"),
328   'destroy': (DestroyCluster, ARGS_NONE,
329               [DEBUG_OPT,
330                make_option("--yes-do-it", dest="yes_do_it",
331                            help="Destroy cluster",
332                            action="store_true"),
333               ],
334               "", "Destroy cluster"),
335   'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
336                "<new_name>",
337                "Renames the cluster"),
338   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT],
339              "", "Does a check on the cluster configuration"),
340   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
341                    "", "Does a check on the cluster disk status"),
342   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
343                      "", "Makes the current node the master"),
344   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
345               "", "Shows the cluster version"),
346   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
347                 "", "Shows the cluster master"),
348   'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
349                "[-n node...] <filename>",
350                "Copies a file to all (or only some) nodes"),
351   'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
352               "[-n node...] <command>",
353               "Runs a command on all (or only some) nodes"),
354   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
355                  "", "Show cluster configuration"),
356   'list-tags': (ListTags, ARGS_NONE,
357                 [DEBUG_OPT], "", "List the tags of the cluster"),
358   'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
359                "tag...", "Add tags to the cluster"),
360   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
361                   "tag...", "Remove tags from the cluster"),
362   'search-tags': (SearchTags, ARGS_ONE,
363                   [DEBUG_OPT], "", "Searches the tags on all objects on"
364                   " the cluster for a given pattern (regex)"),
365   }
366
367 if __name__ == '__main__':
368   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))