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