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