gnt-debug: Extend job queue tests
[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 """Cluster related commands"""
22
23 # pylint: disable-msg=W0401,W0613,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0613: Unused argument, since all functions follow the same API
26 # W0614: Unused import %s from wildcard import (since we need cli)
27 # C0103: Invalid name gnt-cluster
28
29 import sys
30 import os.path
31 import time
32 import OpenSSL
33
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import utils
39 from ganeti import bootstrap
40 from ganeti import ssh
41 from ganeti import objects
42 from ganeti import uidpool
43 from ganeti import compat
44
45
46 @UsesRPC
47 def InitCluster(opts, args):
48   """Initialize the cluster.
49
50   @param opts: the command line options selected by the user
51   @type args: list
52   @param args: should contain only one element, the desired
53       cluster name
54   @rtype: int
55   @return: the desired exit code
56
57   """
58   if not opts.lvm_storage and opts.vg_name:
59     ToStderr("Options --no-lvm-storage and --vg-name conflict.")
60     return 1
61
62   vg_name = opts.vg_name
63   if opts.lvm_storage and not opts.vg_name:
64     vg_name = constants.DEFAULT_VG
65
66   if not opts.drbd_storage and opts.drbd_helper:
67     ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
68     return 1
69
70   drbd_helper = opts.drbd_helper
71   if opts.drbd_storage and not opts.drbd_helper:
72     drbd_helper = constants.DEFAULT_DRBD_HELPER
73
74   hvlist = opts.enabled_hypervisors
75   if hvlist is None:
76     hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
77   hvlist = hvlist.split(",")
78
79   hvparams = dict(opts.hvparams)
80   beparams = opts.beparams
81   nicparams = opts.nicparams
82
83   # prepare beparams dict
84   beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
85   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
86
87   # prepare nicparams dict
88   nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
89   utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
90
91   # prepare hvparams dict
92   for hv in constants.HYPER_TYPES:
93     if hv not in hvparams:
94       hvparams[hv] = {}
95     hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
96     utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97
98   if opts.candidate_pool_size is None:
99     opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
100
101   if opts.mac_prefix is None:
102     opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
103
104   uid_pool = opts.uid_pool
105   if uid_pool is not None:
106     uid_pool = uidpool.ParseUidPool(uid_pool)
107
108   bootstrap.InitCluster(cluster_name=args[0],
109                         secondary_ip=opts.secondary_ip,
110                         vg_name=vg_name,
111                         mac_prefix=opts.mac_prefix,
112                         master_netdev=opts.master_netdev,
113                         file_storage_dir=opts.file_storage_dir,
114                         enabled_hypervisors=hvlist,
115                         hvparams=hvparams,
116                         beparams=beparams,
117                         nicparams=nicparams,
118                         candidate_pool_size=opts.candidate_pool_size,
119                         modify_etc_hosts=opts.modify_etc_hosts,
120                         modify_ssh_setup=opts.modify_ssh_setup,
121                         maintain_node_health=opts.maintain_node_health,
122                         drbd_helper=drbd_helper,
123                         uid_pool=uid_pool,
124                         default_iallocator=opts.default_iallocator,
125                         )
126   op = opcodes.OpPostInitCluster()
127   SubmitOpCode(op, opts=opts)
128   return 0
129
130
131 @UsesRPC
132 def DestroyCluster(opts, args):
133   """Destroy the cluster.
134
135   @param opts: the command line options selected by the user
136   @type args: list
137   @param args: should be an empty list
138   @rtype: int
139   @return: the desired exit code
140
141   """
142   if not opts.yes_do_it:
143     ToStderr("Destroying a cluster is irreversible. If you really want"
144              " destroy this cluster, supply the --yes-do-it option.")
145     return 1
146
147   op = opcodes.OpDestroyCluster()
148   master = SubmitOpCode(op, opts=opts)
149   # if we reached this, the opcode didn't fail; we can proceed to
150   # shutdown all the daemons
151   bootstrap.FinalizeClusterDestroy(master)
152   return 0
153
154
155 def RenameCluster(opts, args):
156   """Rename the cluster.
157
158   @param opts: the command line options selected by the user
159   @type args: list
160   @param args: should contain only one element, the new cluster name
161   @rtype: int
162   @return: the desired exit code
163
164   """
165   cl = GetClient()
166
167   (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
168
169   new_name = args[0]
170   if not opts.force:
171     usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
172                 " connected over the network to the cluster name, the"
173                 " operation is very dangerous as the IP address will be"
174                 " removed from the node and the change may not go through."
175                 " Continue?") % (cluster_name, new_name)
176     if not AskUser(usertext):
177       return 1
178
179   op = opcodes.OpRenameCluster(name=new_name)
180   result = SubmitOpCode(op, opts=opts, cl=cl)
181
182   ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
183
184   return 0
185
186
187 def RedistributeConfig(opts, args):
188   """Forces push of the cluster configuration.
189
190   @param opts: the command line options selected by the user
191   @type args: list
192   @param args: empty list
193   @rtype: int
194   @return: the desired exit code
195
196   """
197   op = opcodes.OpRedistributeConfig()
198   SubmitOrSend(op, opts)
199   return 0
200
201
202 def ShowClusterVersion(opts, args):
203   """Write version of ganeti software to the standard output.
204
205   @param opts: the command line options selected by the user
206   @type args: list
207   @param args: should be an empty list
208   @rtype: int
209   @return: the desired exit code
210
211   """
212   cl = GetClient()
213   result = cl.QueryClusterInfo()
214   ToStdout("Software version: %s", result["software_version"])
215   ToStdout("Internode protocol: %s", result["protocol_version"])
216   ToStdout("Configuration format: %s", result["config_version"])
217   ToStdout("OS api version: %s", result["os_api_version"])
218   ToStdout("Export interface: %s", result["export_version"])
219   return 0
220
221
222 def ShowClusterMaster(opts, args):
223   """Write name of master node to the standard output.
224
225   @param opts: the command line options selected by the user
226   @type args: list
227   @param args: should be an empty list
228   @rtype: int
229   @return: the desired exit code
230
231   """
232   master = bootstrap.GetMaster()
233   ToStdout(master)
234   return 0
235
236
237 def _PrintGroupedParams(paramsdict, level=1, roman=False):
238   """Print Grouped parameters (be, nic, disk) by group.
239
240   @type paramsdict: dict of dicts
241   @param paramsdict: {group: {param: value, ...}, ...}
242   @type level: int
243   @param level: Level of indention
244
245   """
246   indent = "  " * level
247   for item, val in sorted(paramsdict.items()):
248     if isinstance(val, dict):
249       ToStdout("%s- %s:", indent, item)
250       _PrintGroupedParams(val, level=level + 1, roman=roman)
251     elif roman and isinstance(val, int):
252       ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
253     else:
254       ToStdout("%s  %s: %s", indent, item, val)
255
256
257 def ShowClusterConfig(opts, args):
258   """Shows cluster information.
259
260   @param opts: the command line options selected by the user
261   @type args: list
262   @param args: should be an empty list
263   @rtype: int
264   @return: the desired exit code
265
266   """
267   cl = GetClient()
268   result = cl.QueryClusterInfo()
269
270   ToStdout("Cluster name: %s", result["name"])
271   ToStdout("Cluster UUID: %s", result["uuid"])
272
273   ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
274   ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
275
276   ToStdout("Master node: %s", result["master"])
277
278   ToStdout("Architecture (this node): %s (%s)",
279            result["architecture"][0], result["architecture"][1])
280
281   if result["tags"]:
282     tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
283   else:
284     tags = "(none)"
285
286   ToStdout("Tags: %s", tags)
287
288   ToStdout("Default hypervisor: %s", result["default_hypervisor"])
289   ToStdout("Enabled hypervisors: %s",
290            utils.CommaJoin(result["enabled_hypervisors"]))
291
292   ToStdout("Hypervisor parameters:")
293   _PrintGroupedParams(result["hvparams"])
294
295   ToStdout("OS-specific hypervisor parameters:")
296   _PrintGroupedParams(result["os_hvp"])
297
298   ToStdout("OS parameters:")
299   _PrintGroupedParams(result["osparams"])
300
301   ToStdout("Cluster parameters:")
302   ToStdout("  - candidate pool size: %s",
303             compat.TryToRoman(result["candidate_pool_size"],
304                               convert=opts.roman_integers))
305   ToStdout("  - master netdev: %s", result["master_netdev"])
306   ToStdout("  - lvm volume group: %s", result["volume_group_name"])
307   if result["reserved_lvs"]:
308     reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
309   else:
310     reserved_lvs = "(none)"
311   ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
312   ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
313   ToStdout("  - file storage path: %s", result["file_storage_dir"])
314   ToStdout("  - maintenance of node health: %s",
315            result["maintain_node_health"])
316   ToStdout("  - uid pool: %s",
317             uidpool.FormatUidPool(result["uid_pool"],
318                                   roman=opts.roman_integers))
319   ToStdout("  - default instance allocator: %s", result["default_iallocator"])
320
321   ToStdout("Default instance parameters:")
322   _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
323
324   ToStdout("Default nic parameters:")
325   _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
326
327   return 0
328
329
330 def ClusterCopyFile(opts, args):
331   """Copy a file from master to some nodes.
332
333   @param opts: the command line options selected by the user
334   @type args: list
335   @param args: should contain only one element, the path of
336       the file to be copied
337   @rtype: int
338   @return: the desired exit code
339
340   """
341   filename = args[0]
342   if not os.path.exists(filename):
343     raise errors.OpPrereqError("No such filename '%s'" % filename,
344                                errors.ECODE_INVAL)
345
346   cl = GetClient()
347
348   cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
349
350   results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
351                            secondary_ips=opts.use_replication_network)
352
353   srun = ssh.SshRunner(cluster_name=cluster_name)
354   for node in results:
355     if not srun.CopyFileToNode(node, filename):
356       ToStderr("Copy of file %s to node %s failed", filename, node)
357
358   return 0
359
360
361 def RunClusterCommand(opts, args):
362   """Run a command on some nodes.
363
364   @param opts: the command line options selected by the user
365   @type args: list
366   @param args: should contain the command to be run and its arguments
367   @rtype: int
368   @return: the desired exit code
369
370   """
371   cl = GetClient()
372
373   command = " ".join(args)
374
375   nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
376
377   cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
378                                                     "master_node"])
379
380   srun = ssh.SshRunner(cluster_name=cluster_name)
381
382   # Make sure master node is at list end
383   if master_node in nodes:
384     nodes.remove(master_node)
385     nodes.append(master_node)
386
387   for name in nodes:
388     result = srun.Run(name, "root", command)
389     ToStdout("------------------------------------------------")
390     ToStdout("node: %s", name)
391     ToStdout("%s", result.output)
392     ToStdout("return code = %s", result.exit_code)
393
394   return 0
395
396
397 def VerifyCluster(opts, args):
398   """Verify integrity of cluster, performing various test on nodes.
399
400   @param opts: the command line options selected by the user
401   @type args: list
402   @param args: should be an empty list
403   @rtype: int
404   @return: the desired exit code
405
406   """
407   skip_checks = []
408   if opts.skip_nplusone_mem:
409     skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
410   op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
411                                verbose=opts.verbose,
412                                error_codes=opts.error_codes,
413                                debug_simulate_errors=opts.simulate_errors)
414   if SubmitOpCode(op, opts=opts):
415     return 0
416   else:
417     return 1
418
419
420 def VerifyDisks(opts, args):
421   """Verify integrity of cluster disks.
422
423   @param opts: the command line options selected by the user
424   @type args: list
425   @param args: should be an empty list
426   @rtype: int
427   @return: the desired exit code
428
429   """
430   op = opcodes.OpVerifyDisks()
431   result = SubmitOpCode(op, opts=opts)
432   if not isinstance(result, (list, tuple)) or len(result) != 3:
433     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
434
435   bad_nodes, instances, missing = result
436
437   retcode = constants.EXIT_SUCCESS
438
439   if bad_nodes:
440     for node, text in bad_nodes.items():
441       ToStdout("Error gathering data on node %s: %s",
442                node, utils.SafeEncode(text[-400:]))
443       retcode |= 1
444       ToStdout("You need to fix these nodes first before fixing instances")
445
446   if instances:
447     for iname in instances:
448       if iname in missing:
449         continue
450       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
451       try:
452         ToStdout("Activating disks for instance '%s'", iname)
453         SubmitOpCode(op, opts=opts)
454       except errors.GenericError, err:
455         nret, msg = FormatError(err)
456         retcode |= nret
457         ToStderr("Error activating disks for instance %s: %s", iname, msg)
458
459   if missing:
460     for iname, ival in missing.iteritems():
461       all_missing = compat.all(x[0] in bad_nodes for x in ival)
462       if all_missing:
463         ToStdout("Instance %s cannot be verified as it lives on"
464                  " broken nodes", iname)
465       else:
466         ToStdout("Instance %s has missing logical volumes:", iname)
467         ival.sort()
468         for node, vol in ival:
469           if node in bad_nodes:
470             ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
471           else:
472             ToStdout("\t%s /dev/xenvg/%s", node, vol)
473     ToStdout("You need to run replace_disks for all the above"
474            " instances, if this message persist after fixing nodes.")
475     retcode |= 1
476
477   return retcode
478
479
480 def RepairDiskSizes(opts, args):
481   """Verify sizes of cluster disks.
482
483   @param opts: the command line options selected by the user
484   @type args: list
485   @param args: optional list of instances to restrict check to
486   @rtype: int
487   @return: the desired exit code
488
489   """
490   op = opcodes.OpRepairDiskSizes(instances=args)
491   SubmitOpCode(op, opts=opts)
492
493
494 @UsesRPC
495 def MasterFailover(opts, args):
496   """Failover the master node.
497
498   This command, when run on a non-master node, will cause the current
499   master to cease being master, and the non-master to become new
500   master.
501
502   @param opts: the command line options selected by the user
503   @type args: list
504   @param args: should be an empty list
505   @rtype: int
506   @return: the desired exit code
507
508   """
509   if opts.no_voting:
510     usertext = ("This will perform the failover even if most other nodes"
511                 " are down, or if this node is outdated. This is dangerous"
512                 " as it can lead to a non-consistent cluster. Check the"
513                 " gnt-cluster(8) man page before proceeding. Continue?")
514     if not AskUser(usertext):
515       return 1
516
517   return bootstrap.MasterFailover(no_voting=opts.no_voting)
518
519
520 def MasterPing(opts, args):
521   """Checks if the master is alive.
522
523   @param opts: the command line options selected by the user
524   @type args: list
525   @param args: should be an empty list
526   @rtype: int
527   @return: the desired exit code
528
529   """
530   try:
531     cl = GetClient()
532     cl.QueryClusterInfo()
533     return 0
534   except Exception: # pylint: disable-msg=W0703
535     return 1
536
537
538 def SearchTags(opts, args):
539   """Searches the tags on all the cluster.
540
541   @param opts: the command line options selected by the user
542   @type args: list
543   @param args: should contain only one element, the tag pattern
544   @rtype: int
545   @return: the desired exit code
546
547   """
548   op = opcodes.OpSearchTags(pattern=args[0])
549   result = SubmitOpCode(op, opts=opts)
550   if not result:
551     return 1
552   result = list(result)
553   result.sort()
554   for path, tag in result:
555     ToStdout("%s %s", path, tag)
556
557
558 def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
559                  new_confd_hmac_key, new_cds, cds_filename,
560                  force):
561   """Renews cluster certificates, keys and secrets.
562
563   @type new_cluster_cert: bool
564   @param new_cluster_cert: Whether to generate a new cluster certificate
565   @type new_rapi_cert: bool
566   @param new_rapi_cert: Whether to generate a new RAPI certificate
567   @type rapi_cert_filename: string
568   @param rapi_cert_filename: Path to file containing new RAPI certificate
569   @type new_confd_hmac_key: bool
570   @param new_confd_hmac_key: Whether to generate a new HMAC key
571   @type new_cds: bool
572   @param new_cds: Whether to generate a new cluster domain secret
573   @type cds_filename: string
574   @param cds_filename: Path to file containing new cluster domain secret
575   @type force: bool
576   @param force: Whether to ask user for confirmation
577
578   """
579   if new_rapi_cert and rapi_cert_filename:
580     ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
581              " options can be specified at the same time.")
582     return 1
583
584   if new_cds and cds_filename:
585     ToStderr("Only one of the --new-cluster-domain-secret and"
586              " --cluster-domain-secret options can be specified at"
587              " the same time.")
588     return 1
589
590   if rapi_cert_filename:
591     # Read and verify new certificate
592     try:
593       rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
594
595       OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
596                                       rapi_cert_pem)
597     except Exception, err: # pylint: disable-msg=W0703
598       ToStderr("Can't load new RAPI certificate from %s: %s" %
599                (rapi_cert_filename, str(err)))
600       return 1
601
602     try:
603       OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
604     except Exception, err: # pylint: disable-msg=W0703
605       ToStderr("Can't load new RAPI private key from %s: %s" %
606                (rapi_cert_filename, str(err)))
607       return 1
608
609   else:
610     rapi_cert_pem = None
611
612   if cds_filename:
613     try:
614       cds = utils.ReadFile(cds_filename)
615     except Exception, err: # pylint: disable-msg=W0703
616       ToStderr("Can't load new cluster domain secret from %s: %s" %
617                (cds_filename, str(err)))
618       return 1
619   else:
620     cds = None
621
622   if not force:
623     usertext = ("This requires all daemons on all nodes to be restarted and"
624                 " may take some time. Continue?")
625     if not AskUser(usertext):
626       return 1
627
628   def _RenewCryptoInner(ctx):
629     ctx.feedback_fn("Updating certificates and keys")
630     bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
631                                     new_confd_hmac_key,
632                                     new_cds,
633                                     rapi_cert_pem=rapi_cert_pem,
634                                     cds=cds)
635
636     files_to_copy = []
637
638     if new_cluster_cert:
639       files_to_copy.append(constants.NODED_CERT_FILE)
640
641     if new_rapi_cert or rapi_cert_pem:
642       files_to_copy.append(constants.RAPI_CERT_FILE)
643
644     if new_confd_hmac_key:
645       files_to_copy.append(constants.CONFD_HMAC_KEY)
646
647     if new_cds or cds:
648       files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
649
650     if files_to_copy:
651       for node_name in ctx.nonmaster_nodes:
652         ctx.feedback_fn("Copying %s to %s" %
653                         (", ".join(files_to_copy), node_name))
654         for file_name in files_to_copy:
655           ctx.ssh.CopyFileToNode(node_name, file_name)
656
657   RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
658
659   ToStdout("All requested certificates and keys have been replaced."
660            " Running \"gnt-cluster verify\" now is recommended.")
661
662   return 0
663
664
665 def RenewCrypto(opts, args):
666   """Renews cluster certificates, keys and secrets.
667
668   """
669   return _RenewCrypto(opts.new_cluster_cert,
670                       opts.new_rapi_cert,
671                       opts.rapi_cert,
672                       opts.new_confd_hmac_key,
673                       opts.new_cluster_domain_secret,
674                       opts.cluster_domain_secret,
675                       opts.force)
676
677
678 def SetClusterParams(opts, args):
679   """Modify the cluster.
680
681   @param opts: the command line options selected by the user
682   @type args: list
683   @param args: should be an empty list
684   @rtype: int
685   @return: the desired exit code
686
687   """
688   if not (not opts.lvm_storage or opts.vg_name or
689           not opts.drbd_storage or opts.drbd_helper or
690           opts.enabled_hypervisors or opts.hvparams or
691           opts.beparams or opts.nicparams or
692           opts.candidate_pool_size is not None or
693           opts.uid_pool is not None or
694           opts.maintain_node_health is not None or
695           opts.add_uids is not None or
696           opts.remove_uids is not None or
697           opts.default_iallocator is not None or
698           opts.reserved_lvs is not None):
699     ToStderr("Please give at least one of the parameters.")
700     return 1
701
702   vg_name = opts.vg_name
703   if not opts.lvm_storage and opts.vg_name:
704     ToStderr("Options --no-lvm-storage and --vg-name conflict.")
705     return 1
706
707   if not opts.lvm_storage:
708     vg_name = ""
709
710   drbd_helper = opts.drbd_helper
711   if not opts.drbd_storage and opts.drbd_helper:
712     ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
713     return 1
714
715   if not opts.drbd_storage:
716     drbd_helper = ""
717
718   hvlist = opts.enabled_hypervisors
719   if hvlist is not None:
720     hvlist = hvlist.split(",")
721
722   # a list of (name, dict) we can pass directly to dict() (or [])
723   hvparams = dict(opts.hvparams)
724   for hv_params in hvparams.values():
725     utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
726
727   beparams = opts.beparams
728   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
729
730   nicparams = opts.nicparams
731   utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
732
733
734   mnh = opts.maintain_node_health
735
736   uid_pool = opts.uid_pool
737   if uid_pool is not None:
738     uid_pool = uidpool.ParseUidPool(uid_pool)
739
740   add_uids = opts.add_uids
741   if add_uids is not None:
742     add_uids = uidpool.ParseUidPool(add_uids)
743
744   remove_uids = opts.remove_uids
745   if remove_uids is not None:
746     remove_uids = uidpool.ParseUidPool(remove_uids)
747
748   if opts.reserved_lvs is not None:
749     if opts.reserved_lvs == "":
750       opts.reserved_lvs = []
751     else:
752       opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
753
754   op = opcodes.OpSetClusterParams(vg_name=vg_name,
755                                   drbd_helper=drbd_helper,
756                                   enabled_hypervisors=hvlist,
757                                   hvparams=hvparams,
758                                   os_hvp=None,
759                                   beparams=beparams,
760                                   nicparams=nicparams,
761                                   candidate_pool_size=opts.candidate_pool_size,
762                                   maintain_node_health=mnh,
763                                   uid_pool=uid_pool,
764                                   add_uids=add_uids,
765                                   remove_uids=remove_uids,
766                                   default_iallocator=opts.default_iallocator,
767                                   reserved_lvs=opts.reserved_lvs)
768   SubmitOpCode(op, opts=opts)
769   return 0
770
771
772 def QueueOps(opts, args):
773   """Queue operations.
774
775   @param opts: the command line options selected by the user
776   @type args: list
777   @param args: should contain only one element, the subcommand
778   @rtype: int
779   @return: the desired exit code
780
781   """
782   command = args[0]
783   client = GetClient()
784   if command in ("drain", "undrain"):
785     drain_flag = command == "drain"
786     client.SetQueueDrainFlag(drain_flag)
787   elif command == "info":
788     result = client.QueryConfigValues(["drain_flag"])
789     if result[0]:
790       val = "set"
791     else:
792       val = "unset"
793     ToStdout("The drain flag is %s" % val)
794   else:
795     raise errors.OpPrereqError("Command '%s' is not valid." % command,
796                                errors.ECODE_INVAL)
797
798   return 0
799
800
801 def _ShowWatcherPause(until):
802   if until is None or until < time.time():
803     ToStdout("The watcher is not paused.")
804   else:
805     ToStdout("The watcher is paused until %s.", time.ctime(until))
806
807
808 def WatcherOps(opts, args):
809   """Watcher operations.
810
811   @param opts: the command line options selected by the user
812   @type args: list
813   @param args: should contain only one element, the subcommand
814   @rtype: int
815   @return: the desired exit code
816
817   """
818   command = args[0]
819   client = GetClient()
820
821   if command == "continue":
822     client.SetWatcherPause(None)
823     ToStdout("The watcher is no longer paused.")
824
825   elif command == "pause":
826     if len(args) < 2:
827       raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
828
829     result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
830     _ShowWatcherPause(result)
831
832   elif command == "info":
833     result = client.QueryConfigValues(["watcher_pause"])
834     _ShowWatcherPause(result[0])
835
836   else:
837     raise errors.OpPrereqError("Command '%s' is not valid." % command,
838                                errors.ECODE_INVAL)
839
840   return 0
841
842
843 commands = {
844   'init': (
845     InitCluster, [ArgHost(min=1, max=1)],
846     [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
847      HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
848      NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
849      SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
850      UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
851      DEFAULT_IALLOCATOR_OPT],
852     "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
853   'destroy': (
854     DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
855     "", "Destroy cluster"),
856   'rename': (
857     RenameCluster, [ArgHost(min=1, max=1)],
858     [FORCE_OPT],
859     "<new_name>",
860     "Renames the cluster"),
861   'redist-conf': (
862     RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
863     "", "Forces a push of the configuration file and ssconf files"
864     " to the nodes in the cluster"),
865   'verify': (
866     VerifyCluster, ARGS_NONE,
867     [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
868     "", "Does a check on the cluster configuration"),
869   'verify-disks': (
870     VerifyDisks, ARGS_NONE, [],
871     "", "Does a check on the cluster disk status"),
872   'repair-disk-sizes': (
873     RepairDiskSizes, ARGS_MANY_INSTANCES, [],
874     "", "Updates mismatches in recorded disk sizes"),
875   'master-failover': (
876     MasterFailover, ARGS_NONE, [NOVOTING_OPT],
877     "", "Makes the current node the master"),
878   'master-ping': (
879     MasterPing, ARGS_NONE, [],
880     "", "Checks if the master is alive"),
881   'version': (
882     ShowClusterVersion, ARGS_NONE, [],
883     "", "Shows the cluster version"),
884   'getmaster': (
885     ShowClusterMaster, ARGS_NONE, [],
886     "", "Shows the cluster master"),
887   'copyfile': (
888     ClusterCopyFile, [ArgFile(min=1, max=1)],
889     [NODE_LIST_OPT, USE_REPL_NET_OPT],
890     "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
891   'command': (
892     RunClusterCommand, [ArgCommand(min=1)],
893     [NODE_LIST_OPT],
894     "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
895   'info': (
896     ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
897     "[--roman]", "Show cluster configuration"),
898   'list-tags': (
899     ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
900   'add-tags': (
901     AddTags, [ArgUnknown()], [TAG_SRC_OPT],
902     "tag...", "Add tags to the cluster"),
903   'remove-tags': (
904     RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
905     "tag...", "Remove tags from the cluster"),
906   'search-tags': (
907     SearchTags, [ArgUnknown(min=1, max=1)],
908     [], "", "Searches the tags on all objects on"
909     " the cluster for a given pattern (regex)"),
910   'queue': (
911     QueueOps,
912     [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
913     [], "drain|undrain|info", "Change queue properties"),
914   'watcher': (
915     WatcherOps,
916     [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
917      ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
918     [],
919     "{pause <timespec>|continue|info}", "Change watcher properties"),
920   'modify': (
921     SetClusterParams, ARGS_NONE,
922     [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
923      NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
924      UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
925      NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT],
926     "[opts...]",
927     "Alters the parameters of the cluster"),
928   "renew-crypto": (
929     RenewCrypto, ARGS_NONE,
930     [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
931      NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
932      NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
933     "[opts...]",
934     "Renews cluster certificates, keys and secrets"),
935   }
936
937
938 #: dictionary with aliases for commands
939 aliases = {
940   'masterfailover': 'master-failover',
941 }
942
943
944 if __name__ == '__main__':
945   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
946                        aliases=aliases))