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