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