Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_node.py @ d363797e

History | View | Annotate | Download (24.6 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 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

    
38

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

    
46

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

    
50

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

    
62

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

    
66

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

    
78

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

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

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

    
99
_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
100

    
101

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

    
107

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

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

    
118

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

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

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

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

    
137
  cmd.extend(nodes)
138

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

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

    
146

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

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

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

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

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

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

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

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

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

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

    
205

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

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

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

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

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

    
225

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

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

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

    
239

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

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

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

    
253
  dst_node = opts.dst_node
254
  iallocator = opts.iallocator
255

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

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

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

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

    
293

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

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

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

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

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

    
318
  pinst = utils.NiceSort(pinst)
319

    
320
  retcode = 0
321

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

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

    
340

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

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

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

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

    
356
  pinst = utils.NiceSort(pinst)
357

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

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

    
374

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

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

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

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

    
418
  return 0
419

    
420

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

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

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

    
436

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

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

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

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

    
459

    
460
def PowerNode(opts, args):
461
  """Change/ask power state of a node.
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
  command = args[0]
472
  node = args[1]
473

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

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

    
480
  opcodelist = []
481
  if oob_command == constants.OOB_POWER_OFF:
482
    opcodelist.append(opcodes.OpSetNodeParams(node_name=node, offline=True,
483
                                              auto_promote=opts.auto_promote))
484

    
485
  opcodelist.append(opcodes.OpOobCommand(node_name=node, command=oob_command))
486

    
487
  cli.SetGenericOpcodeOpts(opcodelist, opts)
488

    
489
  job_id = cli.SendJob(opcodelist)
490

    
491
  # We just want the OOB Opcode status
492
  # If it fails PollJob gives us the error message in it
493
  result = cli.PollJob(job_id)[-1]
494

    
495
  if result:
496
    if oob_command == constants.OOB_POWER_STATUS:
497
      text = "The machine is %spowered"
498
      if result[constants.OOB_POWER_STATUS_POWERED]:
499
        result = text % ""
500
      else:
501
        result = text % "not "
502
    ToStderr(result)
503

    
504
  return constants.EXIT_SUCCESS
505

    
506

    
507
def ListVolumes(opts, args):
508
  """List logical volumes on node(s).
509

510
  @param opts: the command line options selected by the user
511
  @type args: list
512
  @param args: should either be an empty list, in which case
513
      we list data for all nodes, or contain a list of nodes
514
      to display data only for those
515
  @rtype: int
516
  @return: the desired exit code
517

518
  """
519
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
520

    
521
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
522
  output = SubmitOpCode(op, opts=opts)
523

    
524
  if not opts.no_headers:
525
    headers = {"node": "Node", "phys": "PhysDev",
526
               "vg": "VG", "name": "Name",
527
               "size": "Size", "instance": "Instance"}
528
  else:
529
    headers = None
530

    
531
  unitfields = ["size"]
532

    
533
  numfields = ["size"]
534

    
535
  data = GenerateTable(separator=opts.separator, headers=headers,
536
                       fields=selected_fields, unitfields=unitfields,
537
                       numfields=numfields, data=output, units=opts.units)
538

    
539
  for line in data:
540
    ToStdout(line)
541

    
542
  return 0
543

    
544

    
545
def ListStorage(opts, args):
546
  """List physical volumes on node(s).
547

548
  @param opts: the command line options selected by the user
549
  @type args: list
550
  @param args: should either be an empty list, in which case
551
      we list data for all nodes, or contain a list of nodes
552
      to display data only for those
553
  @rtype: int
554
  @return: the desired exit code
555

556
  """
557
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
558
  if opts.user_storage_type is None:
559
    opts.user_storage_type = constants.ST_LVM_PV
560

    
561
  storage_type = ConvertStorageType(opts.user_storage_type)
562

    
563
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
564

    
565
  op = opcodes.OpQueryNodeStorage(nodes=args,
566
                                  storage_type=storage_type,
567
                                  output_fields=selected_fields)
568
  output = SubmitOpCode(op, opts=opts)
569

    
570
  if not opts.no_headers:
571
    headers = {
572
      constants.SF_NODE: "Node",
573
      constants.SF_TYPE: "Type",
574
      constants.SF_NAME: "Name",
575
      constants.SF_SIZE: "Size",
576
      constants.SF_USED: "Used",
577
      constants.SF_FREE: "Free",
578
      constants.SF_ALLOCATABLE: "Allocatable",
579
      }
580
  else:
581
    headers = None
582

    
583
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
584
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
585

    
586
  # change raw values to nicer strings
587
  for row in output:
588
    for idx, field in enumerate(selected_fields):
589
      val = row[idx]
590
      if field == constants.SF_ALLOCATABLE:
591
        if val:
592
          val = "Y"
593
        else:
594
          val = "N"
595
      row[idx] = str(val)
596

    
597
  data = GenerateTable(separator=opts.separator, headers=headers,
598
                       fields=selected_fields, unitfields=unitfields,
599
                       numfields=numfields, data=output, units=opts.units)
600

    
601
  for line in data:
602
    ToStdout(line)
603

    
604
  return 0
605

    
606

    
607
def ModifyStorage(opts, args):
608
  """Modify storage volume on a node.
609

610
  @param opts: the command line options selected by the user
611
  @type args: list
612
  @param args: should contain 3 items: node name, storage type and volume name
613
  @rtype: int
614
  @return: the desired exit code
615

616
  """
617
  (node_name, user_storage_type, volume_name) = args
618

    
619
  storage_type = ConvertStorageType(user_storage_type)
620

    
621
  changes = {}
622

    
623
  if opts.allocatable is not None:
624
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
625

    
626
  if changes:
627
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
628
                                     storage_type=storage_type,
629
                                     name=volume_name,
630
                                     changes=changes)
631
    SubmitOpCode(op, opts=opts)
632
  else:
633
    ToStderr("No changes to perform, exiting.")
634

    
635

    
636
def RepairStorage(opts, args):
637
  """Repairs a storage volume on a node.
638

639
  @param opts: the command line options selected by the user
640
  @type args: list
641
  @param args: should contain 3 items: node name, storage type and volume name
642
  @rtype: int
643
  @return: the desired exit code
644

645
  """
646
  (node_name, user_storage_type, volume_name) = args
647

    
648
  storage_type = ConvertStorageType(user_storage_type)
649

    
650
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
651
                                   storage_type=storage_type,
652
                                   name=volume_name,
653
                                   ignore_consistency=opts.ignore_consistency)
654
  SubmitOpCode(op, opts=opts)
655

    
656

    
657
def SetNodeParams(opts, args):
658
  """Modifies a node.
659

660
  @param opts: the command line options selected by the user
661
  @type args: list
662
  @param args: should contain only one element, the node name
663
  @rtype: int
664
  @return: the desired exit code
665

666
  """
667
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
668
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
669
                 opts.ndparams]
670
  if all_changes.count(None) == len(all_changes):
671
    ToStderr("Please give at least one of the parameters.")
672
    return 1
673

    
674
  op = opcodes.OpSetNodeParams(node_name=args[0],
675
                               master_candidate=opts.master_candidate,
676
                               offline=opts.offline,
677
                               drained=opts.drained,
678
                               master_capable=opts.master_capable,
679
                               vm_capable=opts.vm_capable,
680
                               secondary_ip=opts.secondary_ip,
681
                               force=opts.force,
682
                               ndparams=opts.ndparams,
683
                               auto_promote=opts.auto_promote)
684

    
685
  # even if here we process the result, we allow submit only
686
  result = SubmitOrSend(op, opts)
687

    
688
  if result:
689
    ToStdout("Modified node %s", args[0])
690
    for param, data in result:
691
      ToStdout(" - %-5s -> %s", param, data)
692
  return 0
693

    
694

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

    
796

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