Fix some pylint-detected issues on the scripts
[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 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
25
26 import sys
27 from optparse import make_option
28 import os.path
29
30 from ganeti.cli import *
31 from ganeti import opcodes
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti import bootstrap
36 from ganeti import ssh
37
38
39 def InitCluster(opts, args):
40   """Initialize the cluster.
41
42   Args:
43     opts - class with options as members
44     args - list of arguments, expected to be [clustername]
45
46   """
47   if not opts.lvm_storage and opts.vg_name:
48     ToStderr("Options --no-lvm-storage and --vg-name conflict.")
49     return 1
50
51   vg_name = opts.vg_name
52   if opts.lvm_storage and not opts.vg_name:
53     vg_name = constants.DEFAULT_VG
54
55   hvlist = opts.enabled_hypervisors
56   if hvlist is not None:
57     hvlist = hvlist.split(",")
58   else:
59     hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
60
61   hvparams = opts.hvparams
62   if hvparams:
63     # a list of (name, dict) we can pass directly to dict()
64     hvparams = dict(opts.hvparams)
65   else:
66     # otherwise init as empty dict
67     hvparams = {}
68
69   beparams = opts.beparams
70   # check for invalid parameters
71   for parameter in beparams:
72     if parameter not in constants.BES_PARAMETERS:
73       print "Invalid backend parameter: %s" % parameter
74       return 1
75
76   # prepare beparams dict
77   for parameter in constants.BES_PARAMETERS:
78     if parameter not in beparams:
79       beparams[parameter] = constants.BEC_DEFAULTS[parameter]
80
81   # type wrangling
82   try:
83     beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
84   except ValueError:
85     print "%s must be an integer" % constants.BE_VCPUS
86     return 1
87
88   beparams[constants.BE_MEMORY] = utils.ParseUnit(beparams[constants.BE_MEMORY])
89
90   # prepare hvparams dict
91   for hv in constants.HYPER_TYPES:
92     if hv not in hvparams:
93       hvparams[hv] = {}
94     for parameter in constants.HVC_DEFAULTS[hv]:
95       if parameter not in hvparams[hv]:
96         hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
97
98   for hv in hvlist:
99     if hv not in constants.HYPER_TYPES:
100       print "invalid hypervisor: %s" % hv
101       return 1
102
103   bootstrap.InitCluster(cluster_name=args[0],
104                         secondary_ip=opts.secondary_ip,
105                         vg_name=vg_name,
106                         mac_prefix=opts.mac_prefix,
107                         def_bridge=opts.def_bridge,
108                         master_netdev=opts.master_netdev,
109                         file_storage_dir=opts.file_storage_dir,
110                         enabled_hypervisors=hvlist,
111                         hvparams=hvparams,
112                         beparams=beparams)
113   return 0
114
115
116 def DestroyCluster(opts, args):
117   """Destroy the cluster.
118
119   Args:
120     opts - class with options as members
121
122   """
123   if not opts.yes_do_it:
124     ToStderr("Destroying a cluster is irreversible. If you really want"
125              " destroy this cluster, supply the --yes-do-it option.")
126     return 1
127
128   op = opcodes.OpDestroyCluster()
129   master = SubmitOpCode(op)
130   # if we reached this, the opcode didn't fail; we can proceed to
131   # shutdown all the daemons
132   bootstrap.FinalizeClusterDestroy(master)
133   return 0
134
135
136 def RenameCluster(opts, args):
137   """Rename the cluster.
138
139   Args:
140     opts - class with options as members, we use force only
141     args - list of arguments, expected to be [new_name]
142
143   """
144   name = args[0]
145   if not opts.force:
146     usertext = ("This will rename the cluster to '%s'. If you are connected"
147                 " over the network to the cluster name, the operation is very"
148                 " dangerous as the IP address will be removed from the node"
149                 " and the change may not go through. Continue?") % name
150     if not AskUser(usertext):
151       return 1
152
153   op = opcodes.OpRenameCluster(name=name)
154   SubmitOpCode(op)
155   return 0
156
157
158 def ShowClusterVersion(opts, args):
159   """Write version of ganeti software to the standard output.
160
161   Args:
162     opts - class with options as members
163
164   """
165   op = opcodes.OpQueryClusterInfo()
166   result = SubmitOpCode(op)
167   ToStdout("Software version: %s", result["software_version"])
168   ToStdout("Internode protocol: %s", result["protocol_version"])
169   ToStdout("Configuration format: %s", result["config_version"])
170   ToStdout("OS api version: %s", result["os_api_version"])
171   ToStdout("Export interface: %s", result["export_version"])
172   return 0
173
174
175 def ShowClusterMaster(opts, args):
176   """Write name of master node to the standard output.
177
178   Args:
179     opts - class with options as members
180
181   """
182   ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
183   return 0
184
185
186 def ShowClusterConfig(opts, args):
187   """Shows cluster information.
188
189   """
190   op = opcodes.OpQueryClusterInfo()
191   result = SubmitOpCode(op)
192
193   ToStdout("Cluster name: %s", result["name"])
194
195   ToStdout("Master node: %s", result["master"])
196
197   ToStdout("Architecture (this node): %s (%s)",
198            result["architecture"][0], result["architecture"][1])
199
200   ToStdout("Default hypervisor: %s", result["hypervisor_type"])
201   ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
202
203   ToStdout("Hypervisor parameters:")
204   for hv_name, hv_dict in result["hvparams"].items():
205     ToStdout("  - %s:", hv_name)
206     for item, val in hv_dict.iteritems():
207       ToStdout("      %s: %s", item, val)
208
209   ToStdout("Cluster parameters:")
210   for gr_name, gr_dict in result["beparams"].items():
211     ToStdout("  - %s:", gr_name)
212     for item, val in gr_dict.iteritems():
213       ToStdout("      %s: %s", item, val)
214
215   return 0
216
217
218 def ClusterCopyFile(opts, args):
219   """Copy a file from master to some nodes.
220
221   Args:
222     opts - class with options as members
223     args - list containing a single element, the file name
224   Opts used:
225     nodes - list containing the name of target nodes; if empty, all nodes
226
227   """
228   filename = args[0]
229   if not os.path.exists(filename):
230     raise errors.OpPrereqError("No such filename '%s'" % filename)
231
232   cl = GetClient()
233
234   myname = utils.HostInfo().name
235
236   cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
237
238   op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
239   results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
240
241   srun = ssh.SshRunner(cluster_name=cluster_name)
242   for node in results:
243     if not srun.CopyFileToNode(node, filename):
244       ToStderr("Copy of file %s to node %s failed", filename, node)
245
246   return 0
247
248
249 def RunClusterCommand(opts, args):
250   """Run a command on some nodes.
251
252   Args:
253     opts - class with options as members
254     args - the command list as a list
255   Opts used:
256     nodes: list containing the name of target nodes; if empty, all nodes
257
258   """
259   cl = GetClient()
260
261   command = " ".join(args)
262   op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
263   nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
264
265   cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
266                                                     "master_node"])
267
268   srun = ssh.SshRunner(cluster_name=cluster_name)
269
270   # Make sure master node is at list end
271   if master_node in nodes:
272     nodes.remove(master_node)
273     nodes.append(master_node)
274
275   for name in nodes:
276     result = srun.Run(name, "root", command)
277     ToStdout("------------------------------------------------")
278     ToStdout("node: %s", name)
279     ToStdout("%s", result.output)
280     ToStdout("return code = %s", result.exit_code)
281
282   return 0
283
284
285 def VerifyCluster(opts, args):
286   """Verify integrity of cluster, performing various test on nodes.
287
288   Args:
289     opts - class with options as members
290
291   """
292   skip_checks = []
293   if opts.skip_nplusone_mem:
294     skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
295   op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
296   if SubmitOpCode(op):
297     return 0
298   else:
299     return 1
300
301
302 def VerifyDisks(opts, args):
303   """Verify integrity of cluster disks.
304
305   Args:
306     opts - class with options as members
307
308   """
309   op = opcodes.OpVerifyDisks()
310   result = SubmitOpCode(op)
311   if not isinstance(result, (list, tuple)) or len(result) != 4:
312     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
313
314   nodes, nlvm, instances, missing = result
315
316   if nodes:
317     ToStdout("Nodes unreachable or with bad data:")
318     for name in nodes:
319       ToStdout("\t%s", name)
320   retcode = constants.EXIT_SUCCESS
321
322   if nlvm:
323     for node, text in nlvm.iteritems():
324       ToStdout("Error on node %s: LVM error: %s",
325                node, text[-400:].encode('string_escape'))
326       retcode |= 1
327       ToStdout("You need to fix these nodes first before fixing instances")
328
329   if instances:
330     for iname in instances:
331       if iname in missing:
332         continue
333       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
334       try:
335         ToStdout("Activating disks for instance '%s'", iname)
336         SubmitOpCode(op)
337       except errors.GenericError, err:
338         nret, msg = FormatError(err)
339         retcode |= nret
340         ToStderr("Error activating disks for instance %s: %s", iname, msg)
341
342   if missing:
343     for iname, ival in missing.iteritems():
344       all_missing = utils.all(ival, lambda x: x[0] in nlvm)
345       if all_missing:
346         ToStdout("Instance %s cannot be verified as it lives on"
347                  " broken nodes", iname)
348       else:
349         ToStdout("Instance %s has missing logical volumes:", iname)
350         ival.sort()
351         for node, vol in ival:
352           if node in nlvm:
353             ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
354           else:
355             ToStdout("\t%s /dev/xenvg/%s", node, vol)
356     ToStdout("You need to run replace_disks for all the above"
357            " instances, if this message persist after fixing nodes.")
358     retcode |= 1
359
360   return retcode
361
362
363 def MasterFailover(opts, args):
364   """Failover the master node.
365
366   This command, when run on a non-master node, will cause the current
367   master to cease being master, and the non-master to become new
368   master.
369
370   """
371   return bootstrap.MasterFailover()
372
373
374 def SearchTags(opts, args):
375   """Searches the tags on all the cluster.
376
377   """
378   op = opcodes.OpSearchTags(pattern=args[0])
379   result = SubmitOpCode(op)
380   if not result:
381     return 1
382   result = list(result)
383   result.sort()
384   for path, tag in result:
385     ToStdout("%s %s", path, tag)
386
387
388 def SetClusterParams(opts, args):
389   """Modify the cluster.
390
391   Args:
392     opts - class with options as members
393
394   """
395   if not (not opts.lvm_storage or opts.vg_name or
396           opts.enabled_hypervisors or opts.hvparams or
397           opts.beparams):
398     ToStderr("Please give at least one of the parameters.")
399     return 1
400
401   vg_name = opts.vg_name
402   if not opts.lvm_storage and opts.vg_name:
403     ToStdout("Options --no-lvm-storage and --vg-name conflict.")
404     return 1
405
406   hvlist = opts.enabled_hypervisors
407   if hvlist is not None:
408     hvlist = hvlist.split(",")
409
410   hvparams = opts.hvparams
411   if hvparams:
412     # a list of (name, dict) we can pass directly to dict()
413     hvparams = dict(opts.hvparams)
414
415   beparams = opts.beparams
416
417   op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
418                                   enabled_hypervisors=hvlist,
419                                   hvparams=hvparams,
420                                   beparams=beparams)
421   SubmitOpCode(op)
422   return 0
423
424
425 def QueueOps(opts, args):
426   """Queue operations.
427
428   """
429   command = args[0]
430   client = GetClient()
431   if command in ("drain", "undrain"):
432     drain_flag = command == "drain"
433     client.SetQueueDrainFlag(drain_flag)
434   elif command == "info":
435     result = client.QueryConfigValues(["drain_flag"])
436     if result[0]:
437       val = "set"
438     else:
439       val = "unset"
440     ToStdout("The drain flag is %s" % val)
441   return 0
442
443 # this is an option common to more than one command, so we declare
444 # it here and reuse it
445 node_option = make_option("-n", "--node", action="append", dest="nodes",
446                           help="Node to copy to (if not given, all nodes),"
447                                " can be given multiple times",
448                           metavar="<node>", default=[])
449
450 commands = {
451   'init': (InitCluster, ARGS_ONE,
452            [DEBUG_OPT,
453             make_option("-s", "--secondary-ip", dest="secondary_ip",
454                         help="Specify the secondary ip for this node;"
455                         " if given, the entire cluster must have secondary"
456                         " addresses",
457                         metavar="ADDRESS", default=None),
458             make_option("-m", "--mac-prefix", dest="mac_prefix",
459                         help="Specify the mac prefix for the instance IP"
460                         " addresses, in the format XX:XX:XX",
461                         metavar="PREFIX",
462                         default="aa:00:00",),
463             make_option("-g", "--vg-name", dest="vg_name",
464                         help="Specify the volume group name "
465                         " (cluster-wide) for disk allocation [xenvg]",
466                         metavar="VG",
467                         default=None,),
468             make_option("-b", "--bridge", dest="def_bridge",
469                         help="Specify the default bridge name (cluster-wide)"
470                           " to connect the instances to [%s]" %
471                           constants.DEFAULT_BRIDGE,
472                         metavar="BRIDGE",
473                         default=constants.DEFAULT_BRIDGE,),
474             make_option("--master-netdev", dest="master_netdev",
475                         help="Specify the node interface (cluster-wide)"
476                           " on which the master IP address will be added "
477                           " [%s]" % constants.DEFAULT_BRIDGE,
478                         metavar="NETDEV",
479                         default=constants.DEFAULT_BRIDGE,),
480             make_option("--file-storage-dir", dest="file_storage_dir",
481                         help="Specify the default directory (cluster-wide)"
482                              " for storing the file-based disks [%s]" %
483                              constants.DEFAULT_FILE_STORAGE_DIR,
484                         metavar="DIR",
485                         default=constants.DEFAULT_FILE_STORAGE_DIR,),
486             make_option("--no-lvm-storage", dest="lvm_storage",
487                         help="No support for lvm based instances"
488                              " (cluster-wide)",
489                         action="store_false", default=True,),
490             make_option("--enabled-hypervisors", dest="enabled_hypervisors",
491                         help="Comma-separated list of hypervisors",
492                         type="string", default=None),
493             ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
494                        help="Hypervisor and hypervisor options, in the"
495                          " format"
496                        " hypervisor:option=value,option=value,...",
497                        default=[],
498                        action="append",
499                        type="identkeyval"),
500             keyval_option("-B", "--backend-parameters", dest="beparams",
501                           type="keyval", default={},
502                           help="Backend parameters"),
503             ],
504            "[opts...] <cluster_name>",
505            "Initialises a new cluster configuration"),
506   'destroy': (DestroyCluster, ARGS_NONE,
507               [DEBUG_OPT,
508                make_option("--yes-do-it", dest="yes_do_it",
509                            help="Destroy cluster",
510                            action="store_true"),
511               ],
512               "", "Destroy cluster"),
513   'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
514                "<new_name>",
515                "Renames the cluster"),
516   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
517              make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
518                          help="Skip N+1 memory redundancy tests",
519                          action="store_true",
520                          default=False,),
521              ],
522              "", "Does a check on the cluster configuration"),
523   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
524                    "", "Does a check on the cluster disk status"),
525   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
526                      "", "Makes the current node the master"),
527   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
528               "", "Shows the cluster version"),
529   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
530                 "", "Shows the cluster master"),
531   'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
532                "[-n node...] <filename>",
533                "Copies a file to all (or only some) nodes"),
534   'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
535               "[-n node...] <command>",
536               "Runs a command on all (or only some) nodes"),
537   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
538                  "", "Show cluster configuration"),
539   'list-tags': (ListTags, ARGS_NONE,
540                 [DEBUG_OPT], "", "List the tags of the cluster"),
541   'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
542                "tag...", "Add tags to the cluster"),
543   'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
544                   "tag...", "Remove tags from the cluster"),
545   'search-tags': (SearchTags, ARGS_ONE,
546                   [DEBUG_OPT], "", "Searches the tags on all objects on"
547                   " the cluster for a given pattern (regex)"),
548   'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
549             "drain|undrain|info", "Change queue properties"),
550   'modify': (SetClusterParams, ARGS_NONE,
551              [DEBUG_OPT,
552               make_option("-g", "--vg-name", dest="vg_name",
553                           help="Specify the volume group name "
554                           " (cluster-wide) for disk allocation "
555                           "and enable lvm based storage",
556                           metavar="VG",),
557               make_option("--no-lvm-storage", dest="lvm_storage",
558                           help="Disable support for lvm based instances"
559                                " (cluster-wide)",
560                           action="store_false", default=True,),
561               make_option("--enabled-hypervisors", dest="enabled_hypervisors",
562                           help="Comma-separated list of hypervisors",
563                           type="string", default=None),
564               ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
565                          help="Hypervisor and hypervisor options, in the"
566                          " format"
567                          " hypervisor:option=value,option=value,...",
568                          default=[],
569                          action="append",
570                          type="identkeyval"),
571               keyval_option("-B", "--backend-parameters", dest="beparams",
572                             type="keyval", default={},
573                             help="Backend parameters"),
574               ],
575              "[opts...]",
576              "Alters the parameters of the cluster"),
577   }
578
579 if __name__ == '__main__':
580   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))