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 from ganeti import confd
42 from ganeti.confd import client as confd_client
43
44
45 _LIST_DEF_FIELDS = [
46 "name", "dtotal", "dfree",
47 "mtotal", "mnode", "mfree",
48 "pinst_cnt", "sinst_cnt",
49 ]
50
51
52
53 _LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
54
55
56
57 _LIST_STOR_DEF_FIELDS = [
58 constants.SF_NODE,
59 constants.SF_TYPE,
60 constants.SF_NAME,
61 constants.SF_SIZE,
62 constants.SF_USED,
63 constants.SF_FREE,
64 constants.SF_ALLOCATABLE,
65 ]
66
67
68
69 _LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
70
71
72
73 _LIST_STOR_HEADERS = {
74 constants.SF_NODE: "Node",
75 constants.SF_TYPE: "Type",
76 constants.SF_NAME: "Name",
77 constants.SF_SIZE: "Size",
78 constants.SF_USED: "Used",
79 constants.SF_FREE: "Free",
80 constants.SF_ALLOCATABLE: "Allocatable",
81 }
82
83
84
85 _USER_STORAGE_TYPE = {
86 constants.ST_FILE: "file",
87 constants.ST_LVM_PV: "lvm-pv",
88 constants.ST_LVM_VG: "lvm-vg",
89 }
90
91 _STORAGE_TYPE_OPT = \
92 cli_option("-t", "--storage-type",
93 dest="user_storage_type",
94 choices=_USER_STORAGE_TYPE.keys(),
95 default=None,
96 metavar="STORAGE_TYPE",
97 help=("Storage type (%s)" %
98 utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
99
100 _REPAIRABLE_STORAGE_TYPES = \
101 [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
102 if constants.SO_FIX_CONSISTENCY in so]
103
104 _MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
105
106
107 _OOB_COMMAND_ASK = frozenset([constants.OOB_POWER_OFF,
108 constants.OOB_POWER_CYCLE])
109
110
111 _ENV_OVERRIDE = frozenset(["list"])
112
113
114 NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
115 action="store_false", dest="node_setup",
116 help=("Do not make initial SSH setup on remote"
117 " node (needs to be done manually)"))
118
119 IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False,
120 action="store_true", dest="ignore_status",
121 help=("Ignore the Node(s) offline status"
122 " (potentially DANGEROUS)"))
134
137 """Wrapper around utils.RunCmd to call setup-ssh
138
139 @param options: The command line options
140 @param nodes: The nodes to setup
141
142 """
143
144 assert nodes, "Empty node list"
145
146 cmd = [constants.SETUP_SSH]
147
148
149
150 if options.debug:
151 cmd.append("--debug")
152 elif options.verbose:
153 cmd.append("--verbose")
154 if not options.ssh_key_check:
155 cmd.append("--no-ssh-key-check")
156 if options.force_join:
157 cmd.append("--force-join")
158
159 cmd.extend(nodes)
160
161 result = utils.RunCmd(cmd, interactive=True)
162
163 if result.failed:
164 errmsg = ("Command '%s' failed with exit code %s; output %r" %
165 (result.cmd, result.exit_code, result.output))
166 raise errors.OpExecError(errmsg)
167
168
169 @UsesRPC
170 -def AddNode(opts, args):
171 """Add a node to the cluster.
172
173 @param opts: the command line options selected by the user
174 @type args: list
175 @param args: should contain only one element, the new node name
176 @rtype: int
177 @return: the desired exit code
178
179 """
180 cl = GetClient()
181 node = netutils.GetHostname(name=args[0]).name
182 readd = opts.readd
183
184 try:
185 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
186 use_locking=False)
187 node_exists, sip, is_master = output[0]
188 except (errors.OpPrereqError, errors.OpExecError):
189 node_exists = ""
190 sip = None
191
192 if readd:
193 if not node_exists:
194 ToStderr("Node %s not in the cluster"
195 " - please retry without '--readd'", node)
196 return 1
197 if is_master:
198 ToStderr("Node %s is the master, cannot readd", node)
199 return 1
200 else:
201 if node_exists:
202 ToStderr("Node %s already in the cluster (as %s)"
203 " - please retry with '--readd'", node, node_exists)
204 return 1
205 sip = opts.secondary_ip
206
207
208 output = cl.QueryConfigValues(["cluster_name"])
209 cluster_name = output[0]
210
211 if not readd and opts.node_setup:
212 ToStderr("-- WARNING -- \n"
213 "Performing this operation is going to replace the ssh daemon"
214 " keypair\n"
215 "on the target machine (%s) with the ones of the"
216 " current one\n"
217 "and grant full intra-cluster ssh root access to/from it\n", node)
218
219 if opts.node_setup:
220 _RunSetupSSH(opts, [node])
221
222 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
223
224 if opts.disk_state:
225 disk_state = utils.FlatToDict(opts.disk_state)
226 else:
227 disk_state = {}
228
229 hv_state = dict(opts.hv_state)
230
231 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
232 readd=opts.readd, group=opts.nodegroup,
233 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
234 master_capable=opts.master_capable,
235 disk_state=disk_state,
236 hv_state=hv_state)
237 SubmitOpCode(op, opts=opts)
238
241 """List nodes and their properties.
242
243 @param opts: the command line options selected by the user
244 @type args: list
245 @param args: nodes to list, or empty for all
246 @rtype: int
247 @return: the desired exit code
248
249 """
250 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
251
252 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
253 (",".join, False))
254
255 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
256 opts.separator, not opts.no_headers,
257 format_override=fmtoverride, verbose=opts.verbose,
258 force_filter=opts.force_filter)
259
262 """List node fields.
263
264 @param opts: the command line options selected by the user
265 @type args: list
266 @param args: fields to list, or empty for all
267 @rtype: int
268 @return: the desired exit code
269
270 """
271 return GenericListFields(constants.QR_NODE, args, opts.separator,
272 not opts.no_headers)
273
276 """Relocate all secondary instance from a node.
277
278 @param opts: the command line options selected by the user
279 @type args: list
280 @param args: should be an empty list
281 @rtype: int
282 @return: the desired exit code
283
284 """
285 if opts.dst_node is not None:
286 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
287 " secondary instances only.")
288 opts.secondary_only = True
289 opts.primary_only = False
290
291 if opts.secondary_only and opts.primary_only:
292 raise errors.OpPrereqError("Only one of the --primary-only and"
293 " --secondary-only options can be passed",
294 errors.ECODE_INVAL)
295 elif opts.primary_only:
296 mode = constants.NODE_EVAC_PRI
297 elif opts.secondary_only:
298 mode = constants.NODE_EVAC_SEC
299 else:
300 mode = constants.NODE_EVAC_ALL
301
302
303 fields = []
304
305 if not opts.secondary_only:
306 fields.append("pinst_list")
307 if not opts.primary_only:
308 fields.append("sinst_list")
309
310 cl = GetClient()
311
312 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
313 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
314
315 if not instances:
316
317 ToStderr("No instances to evacuate on node(s) %s, exiting.",
318 utils.CommaJoin(args))
319 return constants.EXIT_SUCCESS
320
321 if not (opts.force or
322 AskUser("Relocate instance(s) %s from node(s) %s?" %
323 (utils.CommaJoin(utils.NiceSort(instances)),
324 utils.CommaJoin(args)))):
325 return constants.EXIT_CONFIRMATION
326
327
328 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
329 remote_node=opts.dst_node,
330 iallocator=opts.iallocator,
331 early_release=opts.early_release)
332 result = SubmitOrSend(op, opts, cl=cl)
333
334
335 jex = JobExecutor(cl=cl, opts=opts)
336
337 for (status, job_id) in result[constants.JOB_IDS_KEY]:
338 jex.AddJobId(None, status, job_id)
339
340 results = jex.GetResults()
341 bad_cnt = len([row for row in results if not row[0]])
342 if bad_cnt == 0:
343 ToStdout("All instances evacuated successfully.")
344 rcode = constants.EXIT_SUCCESS
345 else:
346 ToStdout("There were %s errors during the evacuation.", bad_cnt)
347 rcode = constants.EXIT_FAILURE
348
349 return rcode
350
353 """Failover all primary instance on a node.
354
355 @param opts: the command line options selected by the user
356 @type args: list
357 @param args: should be an empty list
358 @rtype: int
359 @return: the desired exit code
360
361 """
362 cl = GetClient()
363 force = opts.force
364 selected_fields = ["name", "pinst_list"]
365
366
367
368 result = cl.QueryNodes(names=args, fields=selected_fields,
369 use_locking=False)
370 node, pinst = result[0]
371
372 if not pinst:
373 ToStderr("No primary instances on node %s, exiting.", node)
374 return 0
375
376 pinst = utils.NiceSort(pinst)
377
378 retcode = 0
379
380 if not force and not AskUser("Fail over instance(s) %s?" %
381 (",".join("'%s'" % name for name in pinst))):
382 return 2
383
384 jex = JobExecutor(cl=cl, opts=opts)
385 for iname in pinst:
386 op = opcodes.OpInstanceFailover(instance_name=iname,
387 ignore_consistency=opts.ignore_consistency,
388 iallocator=opts.iallocator)
389 jex.QueueJob(iname, op)
390 results = jex.GetResults()
391 bad_cnt = len([row for row in results if not row[0]])
392 if bad_cnt == 0:
393 ToStdout("All %d instance(s) failed over successfully.", len(results))
394 else:
395 ToStdout("There were errors during the failover:\n"
396 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
397 return retcode
398
401 """Migrate all primary instance on a node.
402
403 """
404 cl = GetClient()
405 force = opts.force
406 selected_fields = ["name", "pinst_list"]
407
408 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
409 ((node, pinst), ) = result
410
411 if not pinst:
412 ToStdout("No primary instances on node %s, exiting." % node)
413 return 0
414
415 pinst = utils.NiceSort(pinst)
416
417 if not (force or
418 AskUser("Migrate instance(s) %s?" %
419 utils.CommaJoin(utils.NiceSort(pinst)))):
420 return constants.EXIT_CONFIRMATION
421
422
423 if not opts.live and opts.migration_mode is not None:
424 raise errors.OpPrereqError("Only one of the --non-live and "
425 "--migration-mode options can be passed",
426 errors.ECODE_INVAL)
427 if not opts.live:
428 mode = constants.HT_MIGRATION_NONLIVE
429 else:
430 mode = opts.migration_mode
431
432 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
433 iallocator=opts.iallocator,
434 target_node=opts.dst_node,
435 allow_runtime_changes=opts.allow_runtime_chgs,
436 ignore_ipolicy=opts.ignore_ipolicy)
437
438 result = SubmitOrSend(op, opts, cl=cl)
439
440
441 jex = JobExecutor(cl=cl, opts=opts)
442
443 for (status, job_id) in result[constants.JOB_IDS_KEY]:
444 jex.AddJobId(None, status, job_id)
445
446 results = jex.GetResults()
447 bad_cnt = len([row for row in results if not row[0]])
448 if bad_cnt == 0:
449 ToStdout("All instances migrated successfully.")
450 rcode = constants.EXIT_SUCCESS
451 else:
452 ToStdout("There were %s errors during the node migration.", bad_cnt)
453 rcode = constants.EXIT_FAILURE
454
455 return rcode
456
459 """Show node information.
460
461 @param opts: the command line options selected by the user
462 @type args: list
463 @param args: should either be an empty list, in which case
464 we show information about all nodes, or should contain
465 a list of nodes to be queried for information
466 @rtype: int
467 @return: the desired exit code
468
469 """
470 cl = GetClient()
471 result = cl.QueryNodes(fields=["name", "pip", "sip",
472 "pinst_list", "sinst_list",
473 "master_candidate", "drained", "offline",
474 "master_capable", "vm_capable", "powered",
475 "ndparams", "custom_ndparams"],
476 names=args, use_locking=False)
477
478 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
479 master_capable, vm_capable, powered, ndparams,
480 ndparams_custom) in result:
481 ToStdout("Node name: %s", name)
482 ToStdout(" primary ip: %s", primary_ip)
483 ToStdout(" secondary ip: %s", secondary_ip)
484 ToStdout(" master candidate: %s", is_mc)
485 ToStdout(" drained: %s", drained)
486 ToStdout(" offline: %s", offline)
487 if powered is not None:
488 ToStdout(" powered: %s", powered)
489 ToStdout(" master_capable: %s", master_capable)
490 ToStdout(" vm_capable: %s", vm_capable)
491 if vm_capable:
492 if pinst:
493 ToStdout(" primary for instances:")
494 for iname in utils.NiceSort(pinst):
495 ToStdout(" - %s", iname)
496 else:
497 ToStdout(" primary for no instances")
498 if sinst:
499 ToStdout(" secondary for instances:")
500 for iname in utils.NiceSort(sinst):
501 ToStdout(" - %s", iname)
502 else:
503 ToStdout(" secondary for no instances")
504 ToStdout(" node parameters:")
505 buf = StringIO()
506 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
507 ToStdout(buf.getvalue().rstrip("\n"))
508
509 return 0
510
513 """Remove a node from the cluster.
514
515 @param opts: the command line options selected by the user
516 @type args: list
517 @param args: should contain only one element, the name of
518 the node to be removed
519 @rtype: int
520 @return: the desired exit code
521
522 """
523 op = opcodes.OpNodeRemove(node_name=args[0])
524 SubmitOpCode(op, opts=opts)
525 return 0
526
529 """Remove a node from the cluster.
530
531 @param opts: the command line options selected by the user
532 @type args: list
533 @param args: should contain only one element, the name of
534 the node to be removed
535 @rtype: int
536 @return: the desired exit code
537
538 """
539 node = args[0]
540 if (not opts.confirm and
541 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
542 return 2
543
544 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
545 result = SubmitOrSend(op, opts)
546 if result:
547 ToStderr(result)
548 return 0
549
552 """Change/ask power state of a node.
553
554 @param opts: the command line options selected by the user
555 @type args: list
556 @param args: should contain only one element, the name of
557 the node to be removed
558 @rtype: int
559 @return: the desired exit code
560
561 """
562 command = args.pop(0)
563
564 if opts.no_headers:
565 headers = None
566 else:
567 headers = {"node": "Node", "status": "Status"}
568
569 if command not in _LIST_POWER_COMMANDS:
570 ToStderr("power subcommand %s not supported." % command)
571 return constants.EXIT_FAILURE
572
573 oob_command = "power-%s" % command
574
575 if oob_command in _OOB_COMMAND_ASK:
576 if not args:
577 ToStderr("Please provide at least one node for this command")
578 return constants.EXIT_FAILURE
579 elif not opts.force and not ConfirmOperation(args, "nodes",
580 "power %s" % command):
581 return constants.EXIT_FAILURE
582 assert len(args) > 0
583
584 opcodelist = []
585 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
586
587 for node in args:
588 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
589 auto_promote=opts.auto_promote))
590
591 opcodelist.append(opcodes.OpOobCommand(node_names=args,
592 command=oob_command,
593 ignore_status=opts.ignore_status,
594 timeout=opts.oob_timeout,
595 power_delay=opts.power_delay))
596
597 cli.SetGenericOpcodeOpts(opcodelist, opts)
598
599 job_id = cli.SendJob(opcodelist)
600
601
602
603 result = cli.PollJob(job_id)[-1]
604
605 errs = 0
606 data = []
607 for node_result in result:
608 (node_tuple, data_tuple) = node_result
609 (_, node_name) = node_tuple
610 (data_status, data_node) = data_tuple
611 if data_status == constants.RS_NORMAL:
612 if oob_command == constants.OOB_POWER_STATUS:
613 if data_node[constants.OOB_POWER_STATUS_POWERED]:
614 text = "powered"
615 else:
616 text = "unpowered"
617 data.append([node_name, text])
618 else:
619
620 data.append([node_name, "invoked"])
621 else:
622 errs += 1
623 data.append([node_name, cli.FormatResultError(data_status, True)])
624
625 data = GenerateTable(separator=opts.separator, headers=headers,
626 fields=["node", "status"], data=data)
627
628 for line in data:
629 ToStdout(line)
630
631 if errs:
632 return constants.EXIT_FAILURE
633 else:
634 return constants.EXIT_SUCCESS
635
638 """Show health of a node using OOB.
639
640 @param opts: the command line options selected by the user
641 @type args: list
642 @param args: should contain only one element, the name of
643 the node to be removed
644 @rtype: int
645 @return: the desired exit code
646
647 """
648 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
649 timeout=opts.oob_timeout)
650 result = SubmitOpCode(op, opts=opts)
651
652 if opts.no_headers:
653 headers = None
654 else:
655 headers = {"node": "Node", "status": "Status"}
656
657 errs = 0
658 data = []
659 for node_result in result:
660 (node_tuple, data_tuple) = node_result
661 (_, node_name) = node_tuple
662 (data_status, data_node) = data_tuple
663 if data_status == constants.RS_NORMAL:
664 data.append([node_name, "%s=%s" % tuple(data_node[0])])
665 for item, status in data_node[1:]:
666 data.append(["", "%s=%s" % (item, status)])
667 else:
668 errs += 1
669 data.append([node_name, cli.FormatResultError(data_status, True)])
670
671 data = GenerateTable(separator=opts.separator, headers=headers,
672 fields=["node", "status"], data=data)
673
674 for line in data:
675 ToStdout(line)
676
677 if errs:
678 return constants.EXIT_FAILURE
679 else:
680 return constants.EXIT_SUCCESS
681
684 """List logical volumes on node(s).
685
686 @param opts: the command line options selected by the user
687 @type args: list
688 @param args: should either be an empty list, in which case
689 we list data for all nodes, or contain a list of nodes
690 to display data only for those
691 @rtype: int
692 @return: the desired exit code
693
694 """
695 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
696
697 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
698 output = SubmitOpCode(op, opts=opts)
699
700 if not opts.no_headers:
701 headers = {"node": "Node", "phys": "PhysDev",
702 "vg": "VG", "name": "Name",
703 "size": "Size", "instance": "Instance"}
704 else:
705 headers = None
706
707 unitfields = ["size"]
708
709 numfields = ["size"]
710
711 data = GenerateTable(separator=opts.separator, headers=headers,
712 fields=selected_fields, unitfields=unitfields,
713 numfields=numfields, data=output, units=opts.units)
714
715 for line in data:
716 ToStdout(line)
717
718 return 0
719
722 """List physical volumes on node(s).
723
724 @param opts: the command line options selected by the user
725 @type args: list
726 @param args: should either be an empty list, in which case
727 we list data for all nodes, or contain a list of nodes
728 to display data only for those
729 @rtype: int
730 @return: the desired exit code
731
732 """
733
734 if opts.user_storage_type is None:
735 opts.user_storage_type = constants.ST_LVM_PV
736
737 storage_type = ConvertStorageType(opts.user_storage_type)
738
739 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
740
741 op = opcodes.OpNodeQueryStorage(nodes=args,
742 storage_type=storage_type,
743 output_fields=selected_fields)
744 output = SubmitOpCode(op, opts=opts)
745
746 if not opts.no_headers:
747 headers = {
748 constants.SF_NODE: "Node",
749 constants.SF_TYPE: "Type",
750 constants.SF_NAME: "Name",
751 constants.SF_SIZE: "Size",
752 constants.SF_USED: "Used",
753 constants.SF_FREE: "Free",
754 constants.SF_ALLOCATABLE: "Allocatable",
755 }
756 else:
757 headers = None
758
759 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
760 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
761
762
763 for row in output:
764 for idx, field in enumerate(selected_fields):
765 val = row[idx]
766 if field == constants.SF_ALLOCATABLE:
767 if val:
768 val = "Y"
769 else:
770 val = "N"
771 row[idx] = str(val)
772
773 data = GenerateTable(separator=opts.separator, headers=headers,
774 fields=selected_fields, unitfields=unitfields,
775 numfields=numfields, data=output, units=opts.units)
776
777 for line in data:
778 ToStdout(line)
779
780 return 0
781
784 """Modify storage volume on a node.
785
786 @param opts: the command line options selected by the user
787 @type args: list
788 @param args: should contain 3 items: node name, storage type and volume name
789 @rtype: int
790 @return: the desired exit code
791
792 """
793 (node_name, user_storage_type, volume_name) = args
794
795 storage_type = ConvertStorageType(user_storage_type)
796
797 changes = {}
798
799 if opts.allocatable is not None:
800 changes[constants.SF_ALLOCATABLE] = opts.allocatable
801
802 if changes:
803 op = opcodes.OpNodeModifyStorage(node_name=node_name,
804 storage_type=storage_type,
805 name=volume_name,
806 changes=changes)
807 SubmitOrSend(op, opts)
808 else:
809 ToStderr("No changes to perform, exiting.")
810
813 """Repairs a storage volume on a node.
814
815 @param opts: the command line options selected by the user
816 @type args: list
817 @param args: should contain 3 items: node name, storage type and volume name
818 @rtype: int
819 @return: the desired exit code
820
821 """
822 (node_name, user_storage_type, volume_name) = args
823
824 storage_type = ConvertStorageType(user_storage_type)
825
826 op = opcodes.OpRepairNodeStorage(node_name=node_name,
827 storage_type=storage_type,
828 name=volume_name,
829 ignore_consistency=opts.ignore_consistency)
830 SubmitOrSend(op, opts)
831
834 """Modifies a node.
835
836 @param opts: the command line options selected by the user
837 @type args: list
838 @param args: should contain only one element, the node name
839 @rtype: int
840 @return: the desired exit code
841
842 """
843 all_changes = [opts.master_candidate, opts.drained, opts.offline,
844 opts.master_capable, opts.vm_capable, opts.secondary_ip,
845 opts.ndparams]
846 if (all_changes.count(None) == len(all_changes) and
847 not (opts.hv_state or opts.disk_state)):
848 ToStderr("Please give at least one of the parameters.")
849 return 1
850
851 if opts.disk_state:
852 disk_state = utils.FlatToDict(opts.disk_state)
853 else:
854 disk_state = {}
855
856 hv_state = dict(opts.hv_state)
857
858 op = opcodes.OpNodeSetParams(node_name=args[0],
859 master_candidate=opts.master_candidate,
860 offline=opts.offline,
861 drained=opts.drained,
862 master_capable=opts.master_capable,
863 vm_capable=opts.vm_capable,
864 secondary_ip=opts.secondary_ip,
865 force=opts.force,
866 ndparams=opts.ndparams,
867 auto_promote=opts.auto_promote,
868 powered=opts.node_powered,
869 hv_state=hv_state,
870 disk_state=disk_state)
871
872
873 result = SubmitOrSend(op, opts)
874
875 if result:
876 ToStdout("Modified node %s", args[0])
877 for param, data in result:
878 ToStdout(" - %-5s -> %s", param, data)
879 return 0
880
883 """Class holding a reply status for synchronous confd clients.
884
885 """
887 self.failure = True
888 self.answer = False
889
892 """Modifies a node.
893
894 @param opts: the command line options selected by the user
895 @type args: list
896 @param args: should contain only one element, the node name
897 @rtype: int
898 @return: the desired exit code
899
900 """
901 if len(args) != 1:
902 ToStderr("Please give one (and only one) node.")
903 return constants.EXIT_FAILURE
904
905 if not constants.ENABLE_CONFD:
906 ToStderr("Error: this command requires confd support, but it has not"
907 " been enabled at build time.")
908 return constants.EXIT_FAILURE
909
910 if not constants.HS_CONFD:
911 ToStderr("Error: this command requires the Haskell version of confd,"
912 " but it has not been enabled at build time.")
913 return constants.EXIT_FAILURE
914
915 status = ReplyStatus()
916
917 def ListDrbdConfdCallback(reply):
918 """Callback for confd queries"""
919 if reply.type == confd_client.UPCALL_REPLY:
920 answer = reply.server_reply.answer
921 reqtype = reply.orig_request.type
922 if reqtype == constants.CONFD_REQ_NODE_DRBD:
923 if reply.server_reply.status != constants.CONFD_REPL_STATUS_OK:
924 ToStderr("Query gave non-ok status '%s': %s" %
925 (reply.server_reply.status,
926 reply.server_reply.answer))
927 status.failure = True
928 return
929 if not confd.HTNodeDrbd(answer):
930 ToStderr("Invalid response from server: expected %s, got %s",
931 confd.HTNodeDrbd, answer)
932 status.failure = True
933 else:
934 status.failure = False
935 status.answer = answer
936 else:
937 ToStderr("Unexpected reply %s!?", reqtype)
938 status.failure = True
939
940 node = args[0]
941 hmac = utils.ReadFile(constants.CONFD_HMAC_KEY)
942 filter_callback = confd_client.ConfdFilterCallback(ListDrbdConfdCallback)
943 counting_callback = confd_client.ConfdCountingCallback(filter_callback)
944 cf_client = confd_client.ConfdClient(hmac, [constants.IP4_ADDRESS_LOCALHOST],
945 counting_callback)
946 req = confd_client.ConfdClientRequest(type=constants.CONFD_REQ_NODE_DRBD,
947 query=node)
948
949 def DoConfdRequestReply(req):
950 counting_callback.RegisterQuery(req.rsalt)
951 cf_client.SendRequest(req, async=False)
952 while not counting_callback.AllAnswered():
953 if not cf_client.ReceiveReply():
954 ToStderr("Did not receive all expected confd replies")
955 break
956
957 DoConfdRequestReply(req)
958
959 if status.failure:
960 return constants.EXIT_FAILURE
961
962 fields = ["node", "minor", "instance", "disk", "role", "peer"]
963 if opts.no_headers:
964 headers = None
965 else:
966 headers = {"node": "Node", "minor": "Minor", "instance": "Instance",
967 "disk": "Disk", "role": "Role", "peer": "PeerNode"}
968
969 data = GenerateTable(separator=opts.separator, headers=headers,
970 fields=fields, data=sorted(status.answer),
971 numfields=["minor"])
972 for line in data:
973 ToStdout(line)
974
975 return constants.EXIT_SUCCESS
976
977 commands = {
978 "add": (
979 AddNode, [ArgHost(min=1, max=1)],
980 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
981 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
982 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
983 DISK_STATE_OPT],
984 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
985 " [--no-node-setup] [--verbose]"
986 " <node_name>",
987 "Add a node to the cluster"),
988 "evacuate": (
989 EvacuateNode, ARGS_ONE_NODE,
990 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
991 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT, SUBMIT_OPT],
992 "[-f] {-I <iallocator> | -n <dst>} [-p | -s] [options...] <node>",
993 "Relocate the primary and/or secondary instances from a node"),
994 "failover": (
995 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
996 IALLOCATOR_OPT, PRIORITY_OPT],
997 "[-f] <node>",
998 "Stops the primary instances on a node and start them on their"
999 " secondary node (only for instances with drbd disk template)"),
1000 "migrate": (
1001 MigrateNode, ARGS_ONE_NODE,
1002 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
1003 IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT,
1004 NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1005 "[-f] <node>",
1006 "Migrate all the primary instance on a node away from it"
1007 " (only for instances of type drbd)"),
1008 "info": (
1009 ShowNodeConfig, ARGS_MANY_NODES, [],
1010 "[<node_name>...]", "Show information about the node(s)"),
1011 "list": (
1012 ListNodes, ARGS_MANY_NODES,
1013 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1014 FORCE_FILTER_OPT],
1015 "[nodes...]",
1016 "Lists the nodes in the cluster. The available fields can be shown using"
1017 " the \"list-fields\" command (see the man page for details)."
1018 " The default field list is (in order): %s." %
1019 utils.CommaJoin(_LIST_DEF_FIELDS)),
1020 "list-fields": (
1021 ListNodeFields, [ArgUnknown()],
1022 [NOHDR_OPT, SEP_OPT],
1023 "[fields...]",
1024 "Lists all available fields for nodes"),
1025 "modify": (
1026 SetNodeParams, ARGS_ONE_NODE,
1027 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
1028 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
1029 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
1030 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
1031 "<node_name>", "Alters the parameters of a node"),
1032 "powercycle": (
1033 PowercycleNode, ARGS_ONE_NODE,
1034 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1035 "<node_name>", "Tries to forcefully powercycle a node"),
1036 "power": (
1037 PowerNode,
1038 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
1039 ArgNode()],
1040 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
1041 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
1042 "on|off|cycle|status [nodes...]",
1043 "Change power state of node by calling out-of-band helper."),
1044 "remove": (
1045 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
1046 "<node_name>", "Removes a node from the cluster"),
1047 "volumes": (
1048 ListVolumes, [ArgNode()],
1049 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
1050 "[<node_name>...]", "List logical volumes on node(s)"),
1051 "list-storage": (
1052 ListStorage, ARGS_MANY_NODES,
1053 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
1054 PRIORITY_OPT],
1055 "[<node_name>...]", "List physical volumes on node(s). The available"
1056 " fields are (see the man page for details): %s." %
1057 (utils.CommaJoin(_LIST_STOR_HEADERS))),
1058 "modify-storage": (
1059 ModifyStorage,
1060 [ArgNode(min=1, max=1),
1061 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
1062 ArgFile(min=1, max=1)],
1063 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1064 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
1065 "repair-storage": (
1066 RepairStorage,
1067 [ArgNode(min=1, max=1),
1068 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
1069 ArgFile(min=1, max=1)],
1070 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1071 "<node_name> <storage_type> <name>",
1072 "Repairs a storage volume on a node"),
1073 "list-tags": (
1074 ListTags, ARGS_ONE_NODE, [],
1075 "<node_name>", "List the tags of the given node"),
1076 "add-tags": (
1077 AddTags, [ArgNode(min=1, max=1), ArgUnknown()],
1078 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1079 "<node_name> tag...", "Add tags to the given node"),
1080 "remove-tags": (
1081 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
1082 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1083 "<node_name> tag...", "Remove tags from the given node"),
1084 "health": (
1085 Health, ARGS_MANY_NODES,
1086 [NOHDR_OPT, SEP_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
1087 "[<node_name>...]", "List health of node(s) using out-of-band"),
1088 "list-drbd": (
1089 ListDrbd, ARGS_ONE_NODE,
1090 [NOHDR_OPT, SEP_OPT],
1091 "[<node_name>]", "Query the list of used DRBD minors on the given node"),
1092 }
1093
1094
1095 aliases = {
1096 "show": "info",
1097 }
1098
1099
1100 -def Main():
1101 return GenericMain(commands, aliases=aliases,
1102 override={"tag_type": constants.TAG_NODE},
1103 env_override=_ENV_OVERRIDE)
1104