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