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