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