Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_node.py @ 792af3ad

History | View | Annotate | Download (24.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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 bootstrap
31
from ganeti import opcodes
32
from ganeti import utils
33
from ganeti import constants
34
from ganeti import errors
35
from ganeti import netutils
36

    
37

    
38
#: default list of field for L{ListNodes}
39
_LIST_DEF_FIELDS = [
40
  "name", "dtotal", "dfree",
41
  "mtotal", "mnode", "mfree",
42
  "pinst_cnt", "sinst_cnt",
43
  ]
44

    
45

    
46
#: Default field list for L{ListVolumes}
47
_LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
48

    
49

    
50
#: default list of field for L{ListStorage}
51
_LIST_STOR_DEF_FIELDS = [
52
  constants.SF_NODE,
53
  constants.SF_TYPE,
54
  constants.SF_NAME,
55
  constants.SF_SIZE,
56
  constants.SF_USED,
57
  constants.SF_FREE,
58
  constants.SF_ALLOCATABLE,
59
  ]
60

    
61

    
62
#: default list of power commands
63
_LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
64

    
65

    
66
#: headers (and full field list) for L{ListStorage}
67
_LIST_STOR_HEADERS = {
68
  constants.SF_NODE: "Node",
69
  constants.SF_TYPE: "Type",
70
  constants.SF_NAME: "Name",
71
  constants.SF_SIZE: "Size",
72
  constants.SF_USED: "Used",
73
  constants.SF_FREE: "Free",
74
  constants.SF_ALLOCATABLE: "Allocatable",
75
  }
76

    
77

    
78
#: User-facing storage unit types
79
_USER_STORAGE_TYPE = {
80
  constants.ST_FILE: "file",
81
  constants.ST_LVM_PV: "lvm-pv",
82
  constants.ST_LVM_VG: "lvm-vg",
83
  }
84

    
85
_STORAGE_TYPE_OPT = \
86
  cli_option("-t", "--storage-type",
87
             dest="user_storage_type",
88
             choices=_USER_STORAGE_TYPE.keys(),
89
             default=None,
90
             metavar="STORAGE_TYPE",
91
             help=("Storage type (%s)" %
92
                   utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
93

    
94
_REPAIRABLE_STORAGE_TYPES = \
95
  [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
96
   if constants.SO_FIX_CONSISTENCY in so]
97

    
98
_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
99

    
100

    
101
NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
102
                              action="store_false", dest="node_setup",
103
                              help=("Do not make initial SSH setup on remote"
104
                                    " node (needs to be done manually)"))
105

    
106

    
107
def ConvertStorageType(user_storage_type):
108
  """Converts a user storage type to its internal name.
109

110
  """
111
  try:
112
    return _USER_STORAGE_TYPE[user_storage_type]
113
  except KeyError:
114
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
115
                               errors.ECODE_INVAL)
116

    
117

    
118
def _RunSetupSSH(options, nodes):
119
  """Wrapper around utils.RunCmd to call setup-ssh
120

121
  @param options: The command line options
122
  @param nodes: The nodes to setup
123

124
  """
125
  cmd = [constants.SETUP_SSH]
126

    
127
  # Pass --debug|--verbose to the external script if set on our invocation
128
  # --debug overrides --verbose
129
  if options.debug:
130
    cmd.append("--debug")
131
  elif options.verbose:
132
    cmd.append("--verbose")
133
  if not options.ssh_key_check:
134
    cmd.append("--no-ssh-key-check")
135

    
136
  cmd.extend(nodes)
137

    
138
  result = utils.RunCmd(cmd, interactive=True)
139

    
140
  if result.failed:
141
    errmsg = ("Command '%s' failed with exit code %s; output %r" %
142
              (result.cmd, result.exit_code, result.output))
143
    raise errors.OpExecError(errmsg)
144

    
145

    
146
@UsesRPC
147
def AddNode(opts, args):
148
  """Add a node to the cluster.
149

150
  @param opts: the command line options selected by the user
151
  @type args: list
152
  @param args: should contain only one element, the new node name
153
  @rtype: int
154
  @return: the desired exit code
155

156
  """
157
  cl = GetClient()
158
  node = netutils.GetHostname(name=args[0]).name
159
  readd = opts.readd
160

    
161
  try:
162
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
163
                           use_locking=False)
164
    node_exists, sip = output[0]
165
  except (errors.OpPrereqError, errors.OpExecError):
166
    node_exists = ""
167
    sip = None
168

    
169
  if readd:
170
    if not node_exists:
171
      ToStderr("Node %s not in the cluster"
172
               " - please retry without '--readd'", node)
173
      return 1
174
  else:
175
    if node_exists:
176
      ToStderr("Node %s already in the cluster (as %s)"
177
               " - please retry with '--readd'", node, node_exists)
178
      return 1
179
    sip = opts.secondary_ip
180

    
181
  # read the cluster name from the master
182
  output = cl.QueryConfigValues(['cluster_name'])
183
  cluster_name = output[0]
184

    
185
  if not readd and opts.node_setup:
186
    ToStderr("-- WARNING -- \n"
187
             "Performing this operation is going to replace the ssh daemon"
188
             " keypair\n"
189
             "on the target machine (%s) with the ones of the"
190
             " current one\n"
191
             "and grant full intra-cluster ssh root access to/from it\n", node)
192

    
193
  if opts.node_setup:
194
    _RunSetupSSH(opts, [node])
195

    
196
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
197

    
198
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
199
                         readd=opts.readd, group=opts.nodegroup,
200
                         vm_capable=opts.vm_capable, ndparams=opts.ndparams,
201
                         master_capable=opts.master_capable)
202
  SubmitOpCode(op, opts=opts)
203

    
204

    
205
def ListNodes(opts, args):
206
  """List nodes and their properties.
207

208
  @param opts: the command line options selected by the user
209
  @type args: list
210
  @param args: nodes to list, or empty for all
211
  @rtype: int
212
  @return: the desired exit code
213

214
  """
215
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
216

    
217
  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
218
                              (",".join, False))
219

    
220
  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
221
                     opts.separator, not opts.no_headers,
222
                     format_override=fmtoverride)
223

    
224

    
225
def ListNodeFields(opts, args):
226
  """List node fields.
227

228
  @param opts: the command line options selected by the user
229
  @type args: list
230
  @param args: fields to list, or empty for all
231
  @rtype: int
232
  @return: the desired exit code
233

234
  """
235
  return GenericListFields(constants.QR_NODE, args, opts.separator,
236
                           not opts.no_headers)
237

    
238

    
239
def EvacuateNode(opts, args):
240
  """Relocate all secondary instance from a node.
241

242
  @param opts: the command line options selected by the user
243
  @type args: list
244
  @param args: should be an empty list
245
  @rtype: int
246
  @return: the desired exit code
247

248
  """
249
  cl = GetClient()
250
  force = opts.force
251

    
252
  dst_node = opts.dst_node
253
  iallocator = opts.iallocator
254

    
255
  op = opcodes.OpNodeEvacuationStrategy(nodes=args,
256
                                        iallocator=iallocator,
257
                                        remote_node=dst_node)
258

    
259
  result = SubmitOpCode(op, cl=cl, opts=opts)
260
  if not result:
261
    # no instances to migrate
262
    ToStderr("No secondary instances on node(s) %s, exiting.",
263
             utils.CommaJoin(args))
264
    return constants.EXIT_SUCCESS
265

    
266
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
267
                               (",".join("'%s'" % name[0] for name in result),
268
                               utils.CommaJoin(args))):
269
    return constants.EXIT_CONFIRMATION
270

    
271
  jex = JobExecutor(cl=cl, opts=opts)
272
  for row in result:
273
    iname = row[0]
274
    node = row[1]
275
    ToStdout("Will relocate instance %s to node %s", iname, node)
276
    op = opcodes.OpReplaceDisks(instance_name=iname,
277
                                remote_node=node, disks=[],
278
                                mode=constants.REPLACE_DISK_CHG,
279
                                early_release=opts.early_release)
280
    jex.QueueJob(iname, op)
281
  results = jex.GetResults()
282
  bad_cnt = len([row for row in results if not row[0]])
283
  if bad_cnt == 0:
284
    ToStdout("All %d instance(s) failed over successfully.", len(results))
285
    rcode = constants.EXIT_SUCCESS
286
  else:
287
    ToStdout("There were errors during the failover:\n"
288
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
289
    rcode = constants.EXIT_FAILURE
290
  return rcode
291

    
292

    
293
def FailoverNode(opts, args):
294
  """Failover all primary instance on a node.
295

296
  @param opts: the command line options selected by the user
297
  @type args: list
298
  @param args: should be an empty list
299
  @rtype: int
300
  @return: the desired exit code
301

302
  """
303
  cl = GetClient()
304
  force = opts.force
305
  selected_fields = ["name", "pinst_list"]
306

    
307
  # these fields are static data anyway, so it doesn't matter, but
308
  # locking=True should be safer
309
  result = cl.QueryNodes(names=args, fields=selected_fields,
310
                         use_locking=False)
311
  node, pinst = result[0]
312

    
313
  if not pinst:
314
    ToStderr("No primary instances on node %s, exiting.", node)
315
    return 0
316

    
317
  pinst = utils.NiceSort(pinst)
318

    
319
  retcode = 0
320

    
321
  if not force and not AskUser("Fail over instance(s) %s?" %
322
                               (",".join("'%s'" % name for name in pinst))):
323
    return 2
324

    
325
  jex = JobExecutor(cl=cl, opts=opts)
326
  for iname in pinst:
327
    op = opcodes.OpFailoverInstance(instance_name=iname,
328
                                    ignore_consistency=opts.ignore_consistency)
329
    jex.QueueJob(iname, op)
330
  results = jex.GetResults()
331
  bad_cnt = len([row for row in results if not row[0]])
332
  if bad_cnt == 0:
333
    ToStdout("All %d instance(s) failed over successfully.", len(results))
334
  else:
335
    ToStdout("There were errors during the failover:\n"
336
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
337
  return retcode
338

    
339

    
340
def MigrateNode(opts, args):
341
  """Migrate all primary instance on a node.
342

343
  """
344
  cl = GetClient()
345
  force = opts.force
346
  selected_fields = ["name", "pinst_list"]
347

    
348
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
349
  node, pinst = result[0]
350

    
351
  if not pinst:
352
    ToStdout("No primary instances on node %s, exiting." % node)
353
    return 0
354

    
355
  pinst = utils.NiceSort(pinst)
356

    
357
  if not force and not AskUser("Migrate instance(s) %s?" %
358
                               (",".join("'%s'" % name for name in pinst))):
359
    return 2
360

    
361
  # this should be removed once --non-live is deprecated
362
  if not opts.live and opts.migration_mode is not None:
363
    raise errors.OpPrereqError("Only one of the --non-live and "
364
                               "--migration-mode options can be passed",
365
                               errors.ECODE_INVAL)
366
  if not opts.live: # --non-live passed
367
    mode = constants.HT_MIGRATION_NONLIVE
368
  else:
369
    mode = opts.migration_mode
370
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
371
  SubmitOpCode(op, cl=cl, opts=opts)
372

    
373

    
374
def ShowNodeConfig(opts, args):
375
  """Show node information.
376

377
  @param opts: the command line options selected by the user
378
  @type args: list
379
  @param args: should either be an empty list, in which case
380
      we show information about all nodes, or should contain
381
      a list of nodes to be queried for information
382
  @rtype: int
383
  @return: the desired exit code
384

385
  """
386
  cl = GetClient()
387
  result = cl.QueryNodes(fields=["name", "pip", "sip",
388
                                 "pinst_list", "sinst_list",
389
                                 "master_candidate", "drained", "offline",
390
                                 "master_capable", "vm_capable"],
391
                         names=args, use_locking=False)
392

    
393
  for (name, primary_ip, secondary_ip, pinst, sinst,
394
       is_mc, drained, offline, master_capable, vm_capable) in result:
395
    ToStdout("Node name: %s", name)
396
    ToStdout("  primary ip: %s", primary_ip)
397
    ToStdout("  secondary ip: %s", secondary_ip)
398
    ToStdout("  master candidate: %s", is_mc)
399
    ToStdout("  drained: %s", drained)
400
    ToStdout("  offline: %s", offline)
401
    ToStdout("  master_capable: %s", master_capable)
402
    ToStdout("  vm_capable: %s", vm_capable)
403
    if vm_capable:
404
      if pinst:
405
        ToStdout("  primary for instances:")
406
        for iname in utils.NiceSort(pinst):
407
          ToStdout("    - %s", iname)
408
      else:
409
        ToStdout("  primary for no instances")
410
      if sinst:
411
        ToStdout("  secondary for instances:")
412
        for iname in utils.NiceSort(sinst):
413
          ToStdout("    - %s", iname)
414
      else:
415
        ToStdout("  secondary for no instances")
416

    
417
  return 0
418

    
419

    
420
def RemoveNode(opts, args):
421
  """Remove a node from the cluster.
422

423
  @param opts: the command line options selected by the user
424
  @type args: list
425
  @param args: should contain only one element, the name of
426
      the node to be removed
427
  @rtype: int
428
  @return: the desired exit code
429

430
  """
431
  op = opcodes.OpRemoveNode(node_name=args[0])
432
  SubmitOpCode(op, opts=opts)
433
  return 0
434

    
435

    
436
def PowercycleNode(opts, args):
437
  """Remove a node from the cluster.
438

439
  @param opts: the command line options selected by the user
440
  @type args: list
441
  @param args: should contain only one element, the name of
442
      the node to be removed
443
  @rtype: int
444
  @return: the desired exit code
445

446
  """
447
  node = args[0]
448
  if (not opts.confirm and
449
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
450
    return 2
451

    
452
  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
453
  result = SubmitOpCode(op, opts=opts)
454
  if result:
455
    ToStderr(result)
456
  return 0
457

    
458

    
459
def PowerNode(opts, args):
460
  """Change/ask power state of a node.
461

462
  @param opts: the command line options selected by the user
463
  @type args: list
464
  @param args: should contain only one element, the name of
465
      the node to be removed
466
  @rtype: int
467
  @return: the desired exit code
468

469
  """
470
  command = args[0]
471
  node = args[1]
472

    
473
  if command not in _LIST_POWER_COMMANDS:
474
    ToStderr("power subcommand %s not supported." % command)
475
    return constants.EXIT_FAILURE
476

    
477
  oob_command = "power-%s" % command
478

    
479
  op = opcodes.OpOobCommand(node_name=node, command=oob_command)
480
  result = SubmitOpCode(op, opts=opts)
481
  if result:
482
    if oob_command == constants.OOB_POWER_STATUS:
483
      text = "The machine is %spowered"
484
      if result[constants.OOB_POWER_STATUS_POWERED]:
485
        result = text % ""
486
      else:
487
        result = text % "not "
488
    ToStderr(result)
489

    
490
  return constants.EXIT_SUCCESS
491

    
492

    
493
def ListVolumes(opts, args):
494
  """List logical volumes on node(s).
495

496
  @param opts: the command line options selected by the user
497
  @type args: list
498
  @param args: should either be an empty list, in which case
499
      we list data for all nodes, or contain a list of nodes
500
      to display data only for those
501
  @rtype: int
502
  @return: the desired exit code
503

504
  """
505
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
506

    
507
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
508
  output = SubmitOpCode(op, opts=opts)
509

    
510
  if not opts.no_headers:
511
    headers = {"node": "Node", "phys": "PhysDev",
512
               "vg": "VG", "name": "Name",
513
               "size": "Size", "instance": "Instance"}
514
  else:
515
    headers = None
516

    
517
  unitfields = ["size"]
518

    
519
  numfields = ["size"]
520

    
521
  data = GenerateTable(separator=opts.separator, headers=headers,
522
                       fields=selected_fields, unitfields=unitfields,
523
                       numfields=numfields, data=output, units=opts.units)
524

    
525
  for line in data:
526
    ToStdout(line)
527

    
528
  return 0
529

    
530

    
531
def ListStorage(opts, args):
532
  """List physical volumes on node(s).
533

534
  @param opts: the command line options selected by the user
535
  @type args: list
536
  @param args: should either be an empty list, in which case
537
      we list data for all nodes, or contain a list of nodes
538
      to display data only for those
539
  @rtype: int
540
  @return: the desired exit code
541

542
  """
543
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
544
  if opts.user_storage_type is None:
545
    opts.user_storage_type = constants.ST_LVM_PV
546

    
547
  storage_type = ConvertStorageType(opts.user_storage_type)
548

    
549
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
550

    
551
  op = opcodes.OpQueryNodeStorage(nodes=args,
552
                                  storage_type=storage_type,
553
                                  output_fields=selected_fields)
554
  output = SubmitOpCode(op, opts=opts)
555

    
556
  if not opts.no_headers:
557
    headers = {
558
      constants.SF_NODE: "Node",
559
      constants.SF_TYPE: "Type",
560
      constants.SF_NAME: "Name",
561
      constants.SF_SIZE: "Size",
562
      constants.SF_USED: "Used",
563
      constants.SF_FREE: "Free",
564
      constants.SF_ALLOCATABLE: "Allocatable",
565
      }
566
  else:
567
    headers = None
568

    
569
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
570
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
571

    
572
  # change raw values to nicer strings
573
  for row in output:
574
    for idx, field in enumerate(selected_fields):
575
      val = row[idx]
576
      if field == constants.SF_ALLOCATABLE:
577
        if val:
578
          val = "Y"
579
        else:
580
          val = "N"
581
      row[idx] = str(val)
582

    
583
  data = GenerateTable(separator=opts.separator, headers=headers,
584
                       fields=selected_fields, unitfields=unitfields,
585
                       numfields=numfields, data=output, units=opts.units)
586

    
587
  for line in data:
588
    ToStdout(line)
589

    
590
  return 0
591

    
592

    
593
def ModifyStorage(opts, args):
594
  """Modify storage volume on a node.
595

596
  @param opts: the command line options selected by the user
597
  @type args: list
598
  @param args: should contain 3 items: node name, storage type and volume name
599
  @rtype: int
600
  @return: the desired exit code
601

602
  """
603
  (node_name, user_storage_type, volume_name) = args
604

    
605
  storage_type = ConvertStorageType(user_storage_type)
606

    
607
  changes = {}
608

    
609
  if opts.allocatable is not None:
610
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
611

    
612
  if changes:
613
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
614
                                     storage_type=storage_type,
615
                                     name=volume_name,
616
                                     changes=changes)
617
    SubmitOpCode(op, opts=opts)
618
  else:
619
    ToStderr("No changes to perform, exiting.")
620

    
621

    
622
def RepairStorage(opts, args):
623
  """Repairs a storage volume on a node.
624

625
  @param opts: the command line options selected by the user
626
  @type args: list
627
  @param args: should contain 3 items: node name, storage type and volume name
628
  @rtype: int
629
  @return: the desired exit code
630

631
  """
632
  (node_name, user_storage_type, volume_name) = args
633

    
634
  storage_type = ConvertStorageType(user_storage_type)
635

    
636
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
637
                                   storage_type=storage_type,
638
                                   name=volume_name,
639
                                   ignore_consistency=opts.ignore_consistency)
640
  SubmitOpCode(op, opts=opts)
641

    
642

    
643
def SetNodeParams(opts, args):
644
  """Modifies a node.
645

646
  @param opts: the command line options selected by the user
647
  @type args: list
648
  @param args: should contain only one element, the node name
649
  @rtype: int
650
  @return: the desired exit code
651

652
  """
653
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
654
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
655
                 opts.ndparams]
656
  if all_changes.count(None) == len(all_changes):
657
    ToStderr("Please give at least one of the parameters.")
658
    return 1
659

    
660
  op = opcodes.OpSetNodeParams(node_name=args[0],
661
                               master_candidate=opts.master_candidate,
662
                               offline=opts.offline,
663
                               drained=opts.drained,
664
                               master_capable=opts.master_capable,
665
                               vm_capable=opts.vm_capable,
666
                               secondary_ip=opts.secondary_ip,
667
                               force=opts.force,
668
                               ndparams=opts.ndparams,
669
                               auto_promote=opts.auto_promote)
670

    
671
  # even if here we process the result, we allow submit only
672
  result = SubmitOrSend(op, opts)
673

    
674
  if result:
675
    ToStdout("Modified node %s", args[0])
676
    for param, data in result:
677
      ToStdout(" - %-5s -> %s", param, data)
678
  return 0
679

    
680

    
681
commands = {
682
  'add': (
683
    AddNode, [ArgHost(min=1, max=1)],
684
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
685
     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
686
     CAPAB_VM_OPT, NODE_PARAMS_OPT],
687
    "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
688
    " <node_name>",
689
    "Add a node to the cluster"),
690
  'evacuate': (
691
    EvacuateNode, [ArgNode(min=1)],
692
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
693
     PRIORITY_OPT],
694
    "[-f] {-I <iallocator> | -n <dst>} <node>",
695
    "Relocate the secondary instances from a node"
696
    " to other nodes (only for instances with drbd disk template)"),
697
  'failover': (
698
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
699
    "[-f] <node>",
700
    "Stops the primary instances on a node and start them on their"
701
    " secondary node (only for instances with drbd disk template)"),
702
  'migrate': (
703
    MigrateNode, ARGS_ONE_NODE,
704
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
705
    "[-f] <node>",
706
    "Migrate all the primary instance on a node away from it"
707
    " (only for instances of type drbd)"),
708
  'info': (
709
    ShowNodeConfig, ARGS_MANY_NODES, [],
710
    "[<node_name>...]", "Show information about the node(s)"),
711
  'list': (
712
    ListNodes, ARGS_MANY_NODES,
713
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
714
    "[nodes...]",
715
    "Lists the nodes in the cluster. The available fields can be shown using"
716
    " the \"list-fields\" command (see the man page for details)."
717
    " The default field list is (in order): %s." %
718
    utils.CommaJoin(_LIST_DEF_FIELDS)),
719
  "list-fields": (
720
    ListNodeFields, [ArgUnknown()],
721
    [NOHDR_OPT, SEP_OPT],
722
    "[fields...]",
723
    "Lists all available fields for nodes"),
724
  'modify': (
725
    SetNodeParams, ARGS_ONE_NODE,
726
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
727
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
728
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT],
729
    "<node_name>", "Alters the parameters of a node"),
730
  'powercycle': (
731
    PowercycleNode, ARGS_ONE_NODE,
732
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
733
    "<node_name>", "Tries to forcefully powercycle a node"),
734
  'power': (
735
    PowerNode,
736
    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
737
     ArgNode(min=1, max=1)],
738
    [], "on|off|cycle|status <node>",
739
    "Change power state of node by calling out-of-band helper."),
740
  'remove': (
741
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
742
    "<node_name>", "Removes a node from the cluster"),
743
  'volumes': (
744
    ListVolumes, [ArgNode()],
745
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
746
    "[<node_name>...]", "List logical volumes on node(s)"),
747
  'list-storage': (
748
    ListStorage, ARGS_MANY_NODES,
749
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
750
     PRIORITY_OPT],
751
    "[<node_name>...]", "List physical volumes on node(s). The available"
752
    " fields are (see the man page for details): %s." %
753
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
754
  'modify-storage': (
755
    ModifyStorage,
756
    [ArgNode(min=1, max=1),
757
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
758
     ArgFile(min=1, max=1)],
759
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
760
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
761
  'repair-storage': (
762
    RepairStorage,
763
    [ArgNode(min=1, max=1),
764
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
765
     ArgFile(min=1, max=1)],
766
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
767
    "<node_name> <storage_type> <name>",
768
    "Repairs a storage volume on a node"),
769
  'list-tags': (
770
    ListTags, ARGS_ONE_NODE, [],
771
    "<node_name>", "List the tags of the given node"),
772
  'add-tags': (
773
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
774
    "<node_name> tag...", "Add tags to the given node"),
775
  'remove-tags': (
776
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
777
    [TAG_SRC_OPT, PRIORITY_OPT],
778
    "<node_name> tag...", "Remove tags from the given node"),
779
  }
780

    
781

    
782
def Main():
783
  return GenericMain(commands, override={"tag_type": constants.TAG_NODE})