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