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