1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Node related commands"""
22
23
24
25
26
27
28
29 import itertools
30
31 from ganeti.cli import *
32 from ganeti import cli
33 from ganeti import bootstrap
34 from ganeti import opcodes
35 from ganeti import utils
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import netutils
39 from cStringIO import StringIO
40
41
42
43 _LIST_DEF_FIELDS = [
44 "name", "dtotal", "dfree",
45 "mtotal", "mnode", "mfree",
46 "pinst_cnt", "sinst_cnt",
47 ]
48
49
50
51 _LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
52
53
54
55 _LIST_STOR_DEF_FIELDS = [
56 constants.SF_NODE,
57 constants.SF_TYPE,
58 constants.SF_NAME,
59 constants.SF_SIZE,
60 constants.SF_USED,
61 constants.SF_FREE,
62 constants.SF_ALLOCATABLE,
63 ]
64
65
66
67 _LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
68
69
70
71 _LIST_STOR_HEADERS = {
72 constants.SF_NODE: "Node",
73 constants.SF_TYPE: "Type",
74 constants.SF_NAME: "Name",
75 constants.SF_SIZE: "Size",
76 constants.SF_USED: "Used",
77 constants.SF_FREE: "Free",
78 constants.SF_ALLOCATABLE: "Allocatable",
79 }
80
81
82
83 _USER_STORAGE_TYPE = {
84 constants.ST_FILE: "file",
85 constants.ST_LVM_PV: "lvm-pv",
86 constants.ST_LVM_VG: "lvm-vg",
87 }
88
89 _STORAGE_TYPE_OPT = \
90 cli_option("-t", "--storage-type",
91 dest="user_storage_type",
92 choices=_USER_STORAGE_TYPE.keys(),
93 default=None,
94 metavar="STORAGE_TYPE",
95 help=("Storage type (%s)" %
96 utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
97
98 _REPAIRABLE_STORAGE_TYPES = \
99 [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
100 if constants.SO_FIX_CONSISTENCY in so]
101
102 _MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
103
104
105 _OOB_COMMAND_ASK = frozenset([constants.OOB_POWER_OFF,
106 constants.OOB_POWER_CYCLE])
107
108
109 NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
110 action="store_false", dest="node_setup",
111 help=("Do not make initial SSH setup on remote"
112 " node (needs to be done manually)"))
113
114 IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False,
115 action="store_true", dest="ignore_status",
116 help=("Ignore the Node(s) offline status"
117 " (potentially DANGEROUS)"))
129
132 """Wrapper around utils.RunCmd to call setup-ssh
133
134 @param options: The command line options
135 @param nodes: The nodes to setup
136
137 """
138 cmd = [constants.SETUP_SSH]
139
140
141
142 if options.debug:
143 cmd.append("--debug")
144 elif options.verbose:
145 cmd.append("--verbose")
146 if not options.ssh_key_check:
147 cmd.append("--no-ssh-key-check")
148 if options.force_join:
149 cmd.append("--force-join")
150
151 cmd.extend(nodes)
152
153 result = utils.RunCmd(cmd, interactive=True)
154
155 if result.failed:
156 errmsg = ("Command '%s' failed with exit code %s; output %r" %
157 (result.cmd, result.exit_code, result.output))
158 raise errors.OpExecError(errmsg)
159
160
161 @UsesRPC
162 -def AddNode(opts, args):
163 """Add a node to the cluster.
164
165 @param opts: the command line options selected by the user
166 @type args: list
167 @param args: should contain only one element, the new node name
168 @rtype: int
169 @return: the desired exit code
170
171 """
172 cl = GetClient()
173 node = netutils.GetHostname(name=args[0]).name
174 readd = opts.readd
175
176 try:
177 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
178 use_locking=False)
179 node_exists, sip, is_master = output[0]
180 except (errors.OpPrereqError, errors.OpExecError):
181 node_exists = ""
182 sip = None
183
184 if readd:
185 if not node_exists:
186 ToStderr("Node %s not in the cluster"
187 " - please retry without '--readd'", node)
188 return 1
189 if is_master:
190 ToStderr("Node %s is the master, cannot readd", node)
191 return 1
192 else:
193 if node_exists:
194 ToStderr("Node %s already in the cluster (as %s)"
195 " - please retry with '--readd'", node, node_exists)
196 return 1
197 sip = opts.secondary_ip
198
199
200 output = cl.QueryConfigValues(["cluster_name"])
201 cluster_name = output[0]
202
203 if not readd and opts.node_setup:
204 ToStderr("-- WARNING -- \n"
205 "Performing this operation is going to replace the ssh daemon"
206 " keypair\n"
207 "on the target machine (%s) with the ones of the"
208 " current one\n"
209 "and grant full intra-cluster ssh root access to/from it\n", node)
210
211 if opts.node_setup:
212 _RunSetupSSH(opts, [node])
213
214 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
215
216 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
217 readd=opts.readd, group=opts.nodegroup,
218 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
219 master_capable=opts.master_capable)
220 SubmitOpCode(op, opts=opts)
221
224 """List nodes and their properties.
225
226 @param opts: the command line options selected by the user
227 @type args: list
228 @param args: nodes to list, or empty for all
229 @rtype: int
230 @return: the desired exit code
231
232 """
233 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
234
235 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
236 (",".join, False))
237
238 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
239 opts.separator, not opts.no_headers,
240 format_override=fmtoverride, verbose=opts.verbose,
241 force_filter=opts.force_filter)
242
245 """List node fields.
246
247 @param opts: the command line options selected by the user
248 @type args: list
249 @param args: fields to list, or empty for all
250 @rtype: int
251 @return: the desired exit code
252
253 """
254 return GenericListFields(constants.QR_NODE, args, opts.separator,
255 not opts.no_headers)
256
259 """Relocate all secondary instance from a node.
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 if opts.dst_node is not None:
269 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
270 " secondary instances only.")
271 opts.secondary_only = True
272 opts.primary_only = False
273
274 if opts.secondary_only and opts.primary_only:
275 raise errors.OpPrereqError("Only one of the --primary-only and"
276 " --secondary-only options can be passed",
277 errors.ECODE_INVAL)
278 elif opts.primary_only:
279 mode = constants.IALLOCATOR_NEVAC_PRI
280 elif opts.secondary_only:
281 mode = constants.IALLOCATOR_NEVAC_SEC
282 else:
283 mode = constants.IALLOCATOR_NEVAC_ALL
284
285
286 fields = []
287
288 if not opts.secondary_only:
289 fields.append("pinst_list")
290 if not opts.primary_only:
291 fields.append("sinst_list")
292
293 cl = GetClient()
294
295 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
296 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
297
298 if not instances:
299
300 ToStderr("No instances to evacuate on node(s) %s, exiting.",
301 utils.CommaJoin(args))
302 return constants.EXIT_SUCCESS
303
304 if not (opts.force or
305 AskUser("Relocate instance(s) %s from node(s) %s?" %
306 (utils.CommaJoin(utils.NiceSort(instances)),
307 utils.CommaJoin(args)))):
308 return constants.EXIT_CONFIRMATION
309
310
311 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
312 remote_node=opts.dst_node,
313 iallocator=opts.iallocator,
314 early_release=opts.early_release)
315 result = SubmitOpCode(op, cl=cl, opts=opts)
316
317
318 jex = JobExecutor(cl=cl, opts=opts)
319
320 for (status, job_id) in result[constants.JOB_IDS_KEY]:
321 jex.AddJobId(None, status, job_id)
322
323 results = jex.GetResults()
324 bad_cnt = len([row for row in results if not row[0]])
325 if bad_cnt == 0:
326 ToStdout("All instances evacuated successfully.")
327 rcode = constants.EXIT_SUCCESS
328 else:
329 ToStdout("There were %s errors during the evacuation.", bad_cnt)
330 rcode = constants.EXIT_FAILURE
331
332 return rcode
333
336 """Failover all primary instance on a node.
337
338 @param opts: the command line options selected by the user
339 @type args: list
340 @param args: should be an empty list
341 @rtype: int
342 @return: the desired exit code
343
344 """
345 cl = GetClient()
346 force = opts.force
347 selected_fields = ["name", "pinst_list"]
348
349
350
351 result = cl.QueryNodes(names=args, fields=selected_fields,
352 use_locking=False)
353 node, pinst = result[0]
354
355 if not pinst:
356 ToStderr("No primary instances on node %s, exiting.", node)
357 return 0
358
359 pinst = utils.NiceSort(pinst)
360
361 retcode = 0
362
363 if not force and not AskUser("Fail over instance(s) %s?" %
364 (",".join("'%s'" % name for name in pinst))):
365 return 2
366
367 jex = JobExecutor(cl=cl, opts=opts)
368 for iname in pinst:
369 op = opcodes.OpInstanceFailover(instance_name=iname,
370 ignore_consistency=opts.ignore_consistency,
371 iallocator=opts.iallocator)
372 jex.QueueJob(iname, op)
373 results = jex.GetResults()
374 bad_cnt = len([row for row in results if not row[0]])
375 if bad_cnt == 0:
376 ToStdout("All %d instance(s) failed over successfully.", len(results))
377 else:
378 ToStdout("There were errors during the failover:\n"
379 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
380 return retcode
381
384 """Migrate all primary instance on a node.
385
386 """
387 cl = GetClient()
388 force = opts.force
389 selected_fields = ["name", "pinst_list"]
390
391 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
392 ((node, pinst), ) = result
393
394 if not pinst:
395 ToStdout("No primary instances on node %s, exiting." % node)
396 return 0
397
398 pinst = utils.NiceSort(pinst)
399
400 if not (force or
401 AskUser("Migrate instance(s) %s?" %
402 utils.CommaJoin(utils.NiceSort(pinst)))):
403 return constants.EXIT_CONFIRMATION
404
405
406 if not opts.live and opts.migration_mode is not None:
407 raise errors.OpPrereqError("Only one of the --non-live and "
408 "--migration-mode options can be passed",
409 errors.ECODE_INVAL)
410 if not opts.live:
411 mode = constants.HT_MIGRATION_NONLIVE
412 else:
413 mode = opts.migration_mode
414
415 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
416 iallocator=opts.iallocator,
417 target_node=opts.dst_node)
418
419 result = SubmitOpCode(op, cl=cl, opts=opts)
420
421
422 jex = JobExecutor(cl=cl, opts=opts)
423
424 for (status, job_id) in result[constants.JOB_IDS_KEY]:
425 jex.AddJobId(None, status, job_id)
426
427 results = jex.GetResults()
428 bad_cnt = len([row for row in results if not row[0]])
429 if bad_cnt == 0:
430 ToStdout("All instances migrated successfully.")
431 rcode = constants.EXIT_SUCCESS
432 else:
433 ToStdout("There were %s errors during the node migration.", bad_cnt)
434 rcode = constants.EXIT_FAILURE
435
436 return rcode
437
440 """Show node information.
441
442 @param opts: the command line options selected by the user
443 @type args: list
444 @param args: should either be an empty list, in which case
445 we show information about all nodes, or should contain
446 a list of nodes to be queried for information
447 @rtype: int
448 @return: the desired exit code
449
450 """
451 cl = GetClient()
452 result = cl.QueryNodes(fields=["name", "pip", "sip",
453 "pinst_list", "sinst_list",
454 "master_candidate", "drained", "offline",
455 "master_capable", "vm_capable", "powered",
456 "ndparams", "custom_ndparams"],
457 names=args, use_locking=False)
458
459 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
460 master_capable, vm_capable, powered, ndparams,
461 ndparams_custom) in result:
462 ToStdout("Node name: %s", name)
463 ToStdout(" primary ip: %s", primary_ip)
464 ToStdout(" secondary ip: %s", secondary_ip)
465 ToStdout(" master candidate: %s", is_mc)
466 ToStdout(" drained: %s", drained)
467 ToStdout(" offline: %s", offline)
468 if powered is not None:
469 ToStdout(" powered: %s", powered)
470 ToStdout(" master_capable: %s", master_capable)
471 ToStdout(" vm_capable: %s", vm_capable)
472 if vm_capable:
473 if pinst:
474 ToStdout(" primary for instances:")
475 for iname in utils.NiceSort(pinst):
476 ToStdout(" - %s", iname)
477 else:
478 ToStdout(" primary for no instances")
479 if sinst:
480 ToStdout(" secondary for instances:")
481 for iname in utils.NiceSort(sinst):
482 ToStdout(" - %s", iname)
483 else:
484 ToStdout(" secondary for no instances")
485 ToStdout(" node parameters:")
486 buf = StringIO()
487 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
488 ToStdout(buf.getvalue().rstrip("\n"))
489
490 return 0
491
494 """Remove a node from the cluster.
495
496 @param opts: the command line options selected by the user
497 @type args: list
498 @param args: should contain only one element, the name of
499 the node to be removed
500 @rtype: int
501 @return: the desired exit code
502
503 """
504 op = opcodes.OpNodeRemove(node_name=args[0])
505 SubmitOpCode(op, opts=opts)
506 return 0
507
510 """Remove a node from the cluster.
511
512 @param opts: the command line options selected by the user
513 @type args: list
514 @param args: should contain only one element, the name of
515 the node to be removed
516 @rtype: int
517 @return: the desired exit code
518
519 """
520 node = args[0]
521 if (not opts.confirm and
522 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
523 return 2
524
525 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
526 result = SubmitOpCode(op, opts=opts)
527 if result:
528 ToStderr(result)
529 return 0
530
533 """Change/ask power state of a node.
534
535 @param opts: the command line options selected by the user
536 @type args: list
537 @param args: should contain only one element, the name of
538 the node to be removed
539 @rtype: int
540 @return: the desired exit code
541
542 """
543 command = args.pop(0)
544
545 if opts.no_headers:
546 headers = None
547 else:
548 headers = {"node": "Node", "status": "Status"}
549
550 if command not in _LIST_POWER_COMMANDS:
551 ToStderr("power subcommand %s not supported." % command)
552 return constants.EXIT_FAILURE
553
554 oob_command = "power-%s" % command
555
556 if oob_command in _OOB_COMMAND_ASK:
557 if not args:
558 ToStderr("Please provide at least one node for this command")
559 return constants.EXIT_FAILURE
560 elif not opts.force and not ConfirmOperation(args, "nodes",
561 "power %s" % command):
562 return constants.EXIT_FAILURE
563 assert len(args) > 0
564
565 opcodelist = []
566 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
567
568 for node in args:
569 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
570 auto_promote=opts.auto_promote))
571
572 opcodelist.append(opcodes.OpOobCommand(node_names=args,
573 command=oob_command,
574 ignore_status=opts.ignore_status,
575 timeout=opts.oob_timeout,
576 power_delay=opts.power_delay))
577
578 cli.SetGenericOpcodeOpts(opcodelist, opts)
579
580 job_id = cli.SendJob(opcodelist)
581
582
583
584 result = cli.PollJob(job_id)[-1]
585
586 errs = 0
587 data = []
588 for node_result in result:
589 (node_tuple, data_tuple) = node_result
590 (_, node_name) = node_tuple
591 (data_status, data_node) = data_tuple
592 if data_status == constants.RS_NORMAL:
593 if oob_command == constants.OOB_POWER_STATUS:
594 if data_node[constants.OOB_POWER_STATUS_POWERED]:
595 text = "powered"
596 else:
597 text = "unpowered"
598 data.append([node_name, text])
599 else:
600
601 data.append([node_name, "invoked"])
602 else:
603 errs += 1
604 data.append([node_name, cli.FormatResultError(data_status, True)])
605
606 data = GenerateTable(separator=opts.separator, headers=headers,
607 fields=["node", "status"], data=data)
608
609 for line in data:
610 ToStdout(line)
611
612 if errs:
613 return constants.EXIT_FAILURE
614 else:
615 return constants.EXIT_SUCCESS
616
619 """Show health of a node using OOB.
620
621 @param opts: the command line options selected by the user
622 @type args: list
623 @param args: should contain only one element, the name of
624 the node to be removed
625 @rtype: int
626 @return: the desired exit code
627
628 """
629 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
630 timeout=opts.oob_timeout)
631 result = SubmitOpCode(op, opts=opts)
632
633 if opts.no_headers:
634 headers = None
635 else:
636 headers = {"node": "Node", "status": "Status"}
637
638 errs = 0
639 data = []
640 for node_result in result:
641 (node_tuple, data_tuple) = node_result
642 (_, node_name) = node_tuple
643 (data_status, data_node) = data_tuple
644 if data_status == constants.RS_NORMAL:
645 data.append([node_name, "%s=%s" % tuple(data_node[0])])
646 for item, status in data_node[1:]:
647 data.append(["", "%s=%s" % (item, status)])
648 else:
649 errs += 1
650 data.append([node_name, cli.FormatResultError(data_status, True)])
651
652 data = GenerateTable(separator=opts.separator, headers=headers,
653 fields=["node", "status"], data=data)
654
655 for line in data:
656 ToStdout(line)
657
658 if errs:
659 return constants.EXIT_FAILURE
660 else:
661 return constants.EXIT_SUCCESS
662
665 """List logical volumes on node(s).
666
667 @param opts: the command line options selected by the user
668 @type args: list
669 @param args: should either be an empty list, in which case
670 we list data for all nodes, or contain a list of nodes
671 to display data only for those
672 @rtype: int
673 @return: the desired exit code
674
675 """
676 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
677
678 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
679 output = SubmitOpCode(op, opts=opts)
680
681 if not opts.no_headers:
682 headers = {"node": "Node", "phys": "PhysDev",
683 "vg": "VG", "name": "Name",
684 "size": "Size", "instance": "Instance"}
685 else:
686 headers = None
687
688 unitfields = ["size"]
689
690 numfields = ["size"]
691
692 data = GenerateTable(separator=opts.separator, headers=headers,
693 fields=selected_fields, unitfields=unitfields,
694 numfields=numfields, data=output, units=opts.units)
695
696 for line in data:
697 ToStdout(line)
698
699 return 0
700
703 """List physical volumes on node(s).
704
705 @param opts: the command line options selected by the user
706 @type args: list
707 @param args: should either be an empty list, in which case
708 we list data for all nodes, or contain a list of nodes
709 to display data only for those
710 @rtype: int
711 @return: the desired exit code
712
713 """
714
715 if opts.user_storage_type is None:
716 opts.user_storage_type = constants.ST_LVM_PV
717
718 storage_type = ConvertStorageType(opts.user_storage_type)
719
720 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
721
722 op = opcodes.OpNodeQueryStorage(nodes=args,
723 storage_type=storage_type,
724 output_fields=selected_fields)
725 output = SubmitOpCode(op, opts=opts)
726
727 if not opts.no_headers:
728 headers = {
729 constants.SF_NODE: "Node",
730 constants.SF_TYPE: "Type",
731 constants.SF_NAME: "Name",
732 constants.SF_SIZE: "Size",
733 constants.SF_USED: "Used",
734 constants.SF_FREE: "Free",
735 constants.SF_ALLOCATABLE: "Allocatable",
736 }
737 else:
738 headers = None
739
740 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
741 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
742
743
744 for row in output:
745 for idx, field in enumerate(selected_fields):
746 val = row[idx]
747 if field == constants.SF_ALLOCATABLE:
748 if val:
749 val = "Y"
750 else:
751 val = "N"
752 row[idx] = str(val)
753
754 data = GenerateTable(separator=opts.separator, headers=headers,
755 fields=selected_fields, unitfields=unitfields,
756 numfields=numfields, data=output, units=opts.units)
757
758 for line in data:
759 ToStdout(line)
760
761 return 0
762
765 """Modify storage volume on a node.
766
767 @param opts: the command line options selected by the user
768 @type args: list
769 @param args: should contain 3 items: node name, storage type and volume name
770 @rtype: int
771 @return: the desired exit code
772
773 """
774 (node_name, user_storage_type, volume_name) = args
775
776 storage_type = ConvertStorageType(user_storage_type)
777
778 changes = {}
779
780 if opts.allocatable is not None:
781 changes[constants.SF_ALLOCATABLE] = opts.allocatable
782
783 if changes:
784 op = opcodes.OpNodeModifyStorage(node_name=node_name,
785 storage_type=storage_type,
786 name=volume_name,
787 changes=changes)
788 SubmitOpCode(op, opts=opts)
789 else:
790 ToStderr("No changes to perform, exiting.")
791
794 """Repairs a storage volume on a node.
795
796 @param opts: the command line options selected by the user
797 @type args: list
798 @param args: should contain 3 items: node name, storage type and volume name
799 @rtype: int
800 @return: the desired exit code
801
802 """
803 (node_name, user_storage_type, volume_name) = args
804
805 storage_type = ConvertStorageType(user_storage_type)
806
807 op = opcodes.OpRepairNodeStorage(node_name=node_name,
808 storage_type=storage_type,
809 name=volume_name,
810 ignore_consistency=opts.ignore_consistency)
811 SubmitOpCode(op, opts=opts)
812
815 """Modifies a node.
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 node name
820 @rtype: int
821 @return: the desired exit code
822
823 """
824 all_changes = [opts.master_candidate, opts.drained, opts.offline,
825 opts.master_capable, opts.vm_capable, opts.secondary_ip,
826 opts.ndparams]
827 if all_changes.count(None) == len(all_changes):
828 ToStderr("Please give at least one of the parameters.")
829 return 1
830
831 op = opcodes.OpNodeSetParams(node_name=args[0],
832 master_candidate=opts.master_candidate,
833 offline=opts.offline,
834 drained=opts.drained,
835 master_capable=opts.master_capable,
836 vm_capable=opts.vm_capable,
837 secondary_ip=opts.secondary_ip,
838 force=opts.force,
839 ndparams=opts.ndparams,
840 auto_promote=opts.auto_promote,
841 powered=opts.node_powered)
842
843
844 result = SubmitOrSend(op, opts)
845
846 if result:
847 ToStdout("Modified node %s", args[0])
848 for param, data in result:
849 ToStdout(" - %-5s -> %s", param, data)
850 return 0
851
852
853 commands = {
854 "add": (
855 AddNode, [ArgHost(min=1, max=1)],
856 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
857 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
858 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT],
859 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
860 " [--no-node-setup] [--verbose]"
861 " <node_name>",
862 "Add a node to the cluster"),
863 "evacuate": (
864 EvacuateNode, ARGS_ONE_NODE,
865 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
866 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT],
867 "[-f] {-I <iallocator> | -n <dst>} <node>",
868 "Relocate the primary and/or secondary instances from a node"),
869 "failover": (
870 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
871 IALLOCATOR_OPT, PRIORITY_OPT],
872 "[-f] <node>",
873 "Stops the primary instances on a node and start them on their"
874 " secondary node (only for instances with drbd disk template)"),
875 "migrate": (
876 MigrateNode, ARGS_ONE_NODE,
877 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
878 IALLOCATOR_OPT, PRIORITY_OPT],
879 "[-f] <node>",
880 "Migrate all the primary instance on a node away from it"
881 " (only for instances of type drbd)"),
882 "info": (
883 ShowNodeConfig, ARGS_MANY_NODES, [],
884 "[<node_name>...]", "Show information about the node(s)"),
885 "list": (
886 ListNodes, ARGS_MANY_NODES,
887 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
888 FORCE_FILTER_OPT],
889 "[nodes...]",
890 "Lists the nodes in the cluster. The available fields can be shown using"
891 " the \"list-fields\" command (see the man page for details)."
892 " The default field list is (in order): %s." %
893 utils.CommaJoin(_LIST_DEF_FIELDS)),
894 "list-fields": (
895 ListNodeFields, [ArgUnknown()],
896 [NOHDR_OPT, SEP_OPT],
897 "[fields...]",
898 "Lists all available fields for nodes"),
899 "modify": (
900 SetNodeParams, ARGS_ONE_NODE,
901 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
902 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
903 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
904 NODE_POWERED_OPT],
905 "<node_name>", "Alters the parameters of a node"),
906 "powercycle": (
907 PowercycleNode, ARGS_ONE_NODE,
908 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
909 "<node_name>", "Tries to forcefully powercycle a node"),
910 "power": (
911 PowerNode,
912 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
913 ArgNode()],
914 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
915 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
916 "on|off|cycle|status [nodes...]",
917 "Change power state of node by calling out-of-band helper."),
918 "remove": (
919 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
920 "<node_name>", "Removes a node from the cluster"),
921 "volumes": (
922 ListVolumes, [ArgNode()],
923 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
924 "[<node_name>...]", "List logical volumes on node(s)"),
925 "list-storage": (
926 ListStorage, ARGS_MANY_NODES,
927 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
928 PRIORITY_OPT],
929 "[<node_name>...]", "List physical volumes on node(s). The available"
930 " fields are (see the man page for details): %s." %
931 (utils.CommaJoin(_LIST_STOR_HEADERS))),
932 "modify-storage": (
933 ModifyStorage,
934 [ArgNode(min=1, max=1),
935 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
936 ArgFile(min=1, max=1)],
937 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
938 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
939 "repair-storage": (
940 RepairStorage,
941 [ArgNode(min=1, max=1),
942 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
943 ArgFile(min=1, max=1)],
944 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
945 "<node_name> <storage_type> <name>",
946 "Repairs a storage volume on a node"),
947 "list-tags": (
948 ListTags, ARGS_ONE_NODE, [],
949 "<node_name>", "List the tags of the given node"),
950 "add-tags": (
951 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
952 "<node_name> tag...", "Add tags to the given node"),
953 "remove-tags": (
954 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
955 [TAG_SRC_OPT, PRIORITY_OPT],
956 "<node_name> tag...", "Remove tags from the given node"),
957 "health": (
958 Health, ARGS_MANY_NODES,
959 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
960 "[<node_name>...]", "List health of node(s) using out-of-band"),
961 }
965 return GenericMain(commands, override={"tag_type": constants.TAG_NODE})
966