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