Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 2e6469a1

History | View | Annotate | Download (23 kB)

1
#!/usr/bin/python
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
import sys
30

    
31
from ganeti.cli import *
32
from ganeti import opcodes
33
from ganeti import utils
34
from ganeti import constants
35
from ganeti import compat
36
from ganeti import errors
37
from ganeti import netutils
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 list of field for L{ListStorage}
49
_LIST_STOR_DEF_FIELDS = [
50
  constants.SF_NODE,
51
  constants.SF_TYPE,
52
  constants.SF_NAME,
53
  constants.SF_SIZE,
54
  constants.SF_USED,
55
  constants.SF_FREE,
56
  constants.SF_ALLOCATABLE,
57
  ]
58

    
59

    
60
#: headers (and full field list for L{ListNodes}
61
_LIST_HEADERS = {
62
  "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
63
  "pinst_list": "PriInstances", "sinst_list": "SecInstances",
64
  "pip": "PrimaryIP", "sip": "SecondaryIP",
65
  "dtotal": "DTotal", "dfree": "DFree",
66
  "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
67
  "bootid": "BootID",
68
  "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
69
  "tags": "Tags",
70
  "serial_no": "SerialNo",
71
  "master_candidate": "MasterC",
72
  "master": "IsMaster",
73
  "offline": "Offline", "drained": "Drained",
74
  "role": "Role",
75
  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID"
76
  }
77

    
78

    
79
#: headers (and full field list for L{ListStorage}
80
_LIST_STOR_HEADERS = {
81
  constants.SF_NODE: "Node",
82
  constants.SF_TYPE: "Type",
83
  constants.SF_NAME: "Name",
84
  constants.SF_SIZE: "Size",
85
  constants.SF_USED: "Used",
86
  constants.SF_FREE: "Free",
87
  constants.SF_ALLOCATABLE: "Allocatable",
88
  }
89

    
90

    
91
#: User-facing storage unit types
92
_USER_STORAGE_TYPE = {
93
  constants.ST_FILE: "file",
94
  constants.ST_LVM_PV: "lvm-pv",
95
  constants.ST_LVM_VG: "lvm-vg",
96
  }
97

    
98
_STORAGE_TYPE_OPT = \
99
  cli_option("-t", "--storage-type",
100
             dest="user_storage_type",
101
             choices=_USER_STORAGE_TYPE.keys(),
102
             default=None,
103
             metavar="STORAGE_TYPE",
104
             help=("Storage type (%s)" %
105
                   utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
106

    
107
_REPAIRABLE_STORAGE_TYPES = \
108
  [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
109
   if constants.SO_FIX_CONSISTENCY in so]
110

    
111
_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
112

    
113

    
114
NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
115
                              action="store_false", dest="node_setup",
116
                              help=("Do not make initial SSH setup on remote"
117
                                    " node (needs to be done manually)"))
118

    
119

    
120
def ConvertStorageType(user_storage_type):
121
  """Converts a user storage type to its internal name.
122

    
123
  """
124
  try:
125
    return _USER_STORAGE_TYPE[user_storage_type]
126
  except KeyError:
127
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
128
                               errors.ECODE_INVAL)
129

    
130

    
131
def _RunSetupSSH(options, nodes):
132
  """Wrapper around utils.RunCmd to call setup-ssh
133

    
134
  @param options: The command line options
135
  @param nodes: The nodes to setup
136

    
137
  """
138
  cmd = [constants.SETUP_SSH]
139

    
140
  # Pass --debug|--verbose to the external script if set on our invocation
141
  # --debug overrides --verbose
142
  if options.debug:
143
    cmd.append("--debug")
144
  elif options.verbose:
145
    cmd.append("--verbose")
146

    
147
  cmd.extend(nodes)
148

    
149
  result = utils.RunCmd(cmd, interactive=True)
150

    
151
  if result.failed:
152
    errmsg = ("Command '%s' failed with exit code %s; output %r" %
153
              (result.cmd, result.exit_code, result.output))
154
    raise errors.OpExecError(errmsg)
155

    
156

    
157
@UsesRPC
158
def AddNode(opts, args):
159
  """Add a node to the cluster.
160

    
161
  @param opts: the command line options selected by the user
162
  @type args: list
163
  @param args: should contain only one element, the new node name
164
  @rtype: int
165
  @return: the desired exit code
166

    
167
  """
168
  cl = GetClient()
169
  node = netutils.GetHostname(name=args[0]).name
170
  readd = opts.readd
171

    
172
  try:
173
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
174
                           use_locking=False)
175
    node_exists, sip = output[0]
176
  except (errors.OpPrereqError, errors.OpExecError):
177
    node_exists = ""
178
    sip = None
179

    
180
  if readd:
181
    if not node_exists:
182
      ToStderr("Node %s not in the cluster"
183
               " - please retry without '--readd'", node)
184
      return 1
185
  else:
186
    if node_exists:
187
      ToStderr("Node %s already in the cluster (as %s)"
188
               " - please retry with '--readd'", node, node_exists)
189
      return 1
190
    sip = opts.secondary_ip
191

    
192
  # read the cluster name from the master
193
  output = cl.QueryConfigValues(['cluster_name'])
194

    
195
  if not readd:
196
    ToStderr("-- WARNING -- \n"
197
             "Performing this operation is going to replace the ssh daemon"
198
             " keypair\n"
199
             "on the target machine (%s) with the ones of the"
200
             " current one\n"
201
             "and grant full intra-cluster ssh root access to/from it\n", node)
202

    
203
  if opts.node_setup:
204
    _RunSetupSSH(opts, [node])
205

    
206
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
207
                         readd=opts.readd)
208
  SubmitOpCode(op, opts=opts)
209

    
210

    
211
def ListNodes(opts, args):
212
  """List nodes and their properties.
213

    
214
  @param opts: the command line options selected by the user
215
  @type args: list
216
  @param args: should be an empty list
217
  @rtype: int
218
  @return: the desired exit code
219

    
220
  """
221
  if opts.output is None:
222
    selected_fields = _LIST_DEF_FIELDS
223
  elif opts.output.startswith("+"):
224
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
225
  else:
226
    selected_fields = opts.output.split(",")
227

    
228
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
229

    
230
  if not opts.no_headers:
231
    headers = _LIST_HEADERS
232
  else:
233
    headers = None
234

    
235
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
236

    
237
  numfields = ["dtotal", "dfree",
238
               "mtotal", "mnode", "mfree",
239
               "pinst_cnt", "sinst_cnt",
240
               "ctotal", "serial_no"]
241

    
242
  list_type_fields = ("pinst_list", "sinst_list", "tags")
243
  # change raw values to nicer strings
244
  for row in output:
245
    for idx, field in enumerate(selected_fields):
246
      val = row[idx]
247
      if field in list_type_fields:
248
        val = ",".join(val)
249
      elif field in ('master', 'master_candidate', 'offline', 'drained'):
250
        if val:
251
          val = 'Y'
252
        else:
253
          val = 'N'
254
      elif field == "ctime" or field == "mtime":
255
        val = utils.FormatTime(val)
256
      elif val is None:
257
        val = "?"
258
      elif opts.roman_integers and isinstance(val, int):
259
        val = compat.TryToRoman(val)
260
      row[idx] = str(val)
261

    
262
  data = GenerateTable(separator=opts.separator, headers=headers,
263
                       fields=selected_fields, unitfields=unitfields,
264
                       numfields=numfields, data=output, units=opts.units)
265
  for line in data:
266
    ToStdout(line)
267

    
268
  return 0
269

    
270

    
271
def EvacuateNode(opts, args):
272
  """Relocate all secondary instance from a node.
273

    
274
  @param opts: the command line options selected by the user
275
  @type args: list
276
  @param args: should be an empty list
277
  @rtype: int
278
  @return: the desired exit code
279

    
280
  """
281
  cl = GetClient()
282
  force = opts.force
283

    
284
  dst_node = opts.dst_node
285
  iallocator = opts.iallocator
286

    
287
  op = opcodes.OpNodeEvacuationStrategy(nodes=args,
288
                                        iallocator=iallocator,
289
                                        remote_node=dst_node)
290

    
291
  result = SubmitOpCode(op, cl=cl, opts=opts)
292
  if not result:
293
    # no instances to migrate
294
    ToStderr("No secondary instances on node(s) %s, exiting.",
295
             utils.CommaJoin(args))
296
    return constants.EXIT_SUCCESS
297

    
298
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
299
                               (",".join("'%s'" % name[0] for name in result),
300
                               utils.CommaJoin(args))):
301
    return constants.EXIT_CONFIRMATION
302

    
303
  jex = JobExecutor(cl=cl, opts=opts)
304
  for row in result:
305
    iname = row[0]
306
    node = row[1]
307
    ToStdout("Will relocate instance %s to node %s", iname, node)
308
    op = opcodes.OpReplaceDisks(instance_name=iname,
309
                                remote_node=node, disks=[],
310
                                mode=constants.REPLACE_DISK_CHG,
311
                                early_release=opts.early_release)
312
    jex.QueueJob(iname, op)
313
  results = jex.GetResults()
314
  bad_cnt = len([row for row in results if not row[0]])
315
  if bad_cnt == 0:
316
    ToStdout("All %d instance(s) failed over successfully.", len(results))
317
    rcode = constants.EXIT_SUCCESS
318
  else:
319
    ToStdout("There were errors during the failover:\n"
320
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
321
    rcode = constants.EXIT_FAILURE
322
  return rcode
323

    
324

    
325
def FailoverNode(opts, args):
326
  """Failover all primary instance on a node.
327

    
328
  @param opts: the command line options selected by the user
329
  @type args: list
330
  @param args: should be an empty list
331
  @rtype: int
332
  @return: the desired exit code
333

    
334
  """
335
  cl = GetClient()
336
  force = opts.force
337
  selected_fields = ["name", "pinst_list"]
338

    
339
  # these fields are static data anyway, so it doesn't matter, but
340
  # locking=True should be safer
341
  result = cl.QueryNodes(names=args, fields=selected_fields,
342
                         use_locking=False)
343
  node, pinst = result[0]
344

    
345
  if not pinst:
346
    ToStderr("No primary instances on node %s, exiting.", node)
347
    return 0
348

    
349
  pinst = utils.NiceSort(pinst)
350

    
351
  retcode = 0
352

    
353
  if not force and not AskUser("Fail over instance(s) %s?" %
354
                               (",".join("'%s'" % name for name in pinst))):
355
    return 2
356

    
357
  jex = JobExecutor(cl=cl, opts=opts)
358
  for iname in pinst:
359
    op = opcodes.OpFailoverInstance(instance_name=iname,
360
                                    ignore_consistency=opts.ignore_consistency)
361
    jex.QueueJob(iname, op)
362
  results = jex.GetResults()
363
  bad_cnt = len([row for row in results if not row[0]])
364
  if bad_cnt == 0:
365
    ToStdout("All %d instance(s) failed over successfully.", len(results))
366
  else:
367
    ToStdout("There were errors during the failover:\n"
368
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
369
  return retcode
370

    
371

    
372
def MigrateNode(opts, args):
373
  """Migrate all primary instance on a node.
374

    
375
  """
376
  cl = GetClient()
377
  force = opts.force
378
  selected_fields = ["name", "pinst_list"]
379

    
380
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
381
  node, pinst = result[0]
382

    
383
  if not pinst:
384
    ToStdout("No primary instances on node %s, exiting." % node)
385
    return 0
386

    
387
  pinst = utils.NiceSort(pinst)
388

    
389
  if not force and not AskUser("Migrate instance(s) %s?" %
390
                               (",".join("'%s'" % name for name in pinst))):
391
    return 2
392

    
393
  # this should be removed once --non-live is deprecated
394
  if not opts.live and opts.migration_mode is not None:
395
    raise errors.OpPrereqError("Only one of the --non-live and "
396
                               "--migration-mode options can be passed",
397
                               errors.ECODE_INVAL)
398
  if not opts.live: # --non-live passed
399
    mode = constants.HT_MIGRATION_NONLIVE
400
  else:
401
    mode = opts.migration_mode
402
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
403
  SubmitOpCode(op, cl=cl, opts=opts)
404

    
405

    
406
def ShowNodeConfig(opts, args):
407
  """Show node information.
408

    
409
  @param opts: the command line options selected by the user
410
  @type args: list
411
  @param args: should either be an empty list, in which case
412
      we show information about all nodes, or should contain
413
      a list of nodes to be queried for information
414
  @rtype: int
415
  @return: the desired exit code
416

    
417
  """
418
  cl = GetClient()
419
  result = cl.QueryNodes(fields=["name", "pip", "sip",
420
                                 "pinst_list", "sinst_list",
421
                                 "master_candidate", "drained", "offline"],
422
                         names=args, use_locking=False)
423

    
424
  for (name, primary_ip, secondary_ip, pinst, sinst,
425
       is_mc, drained, offline) in result:
426
    ToStdout("Node name: %s", name)
427
    ToStdout("  primary ip: %s", primary_ip)
428
    ToStdout("  secondary ip: %s", secondary_ip)
429
    ToStdout("  master candidate: %s", is_mc)
430
    ToStdout("  drained: %s", drained)
431
    ToStdout("  offline: %s", offline)
432
    if pinst:
433
      ToStdout("  primary for instances:")
434
      for iname in utils.NiceSort(pinst):
435
        ToStdout("    - %s", iname)
436
    else:
437
      ToStdout("  primary for no instances")
438
    if sinst:
439
      ToStdout("  secondary for instances:")
440
      for iname in utils.NiceSort(sinst):
441
        ToStdout("    - %s", iname)
442
    else:
443
      ToStdout("  secondary for no instances")
444

    
445
  return 0
446

    
447

    
448
def RemoveNode(opts, args):
449
  """Remove a node from the cluster.
450

    
451
  @param opts: the command line options selected by the user
452
  @type args: list
453
  @param args: should contain only one element, the name of
454
      the node to be removed
455
  @rtype: int
456
  @return: the desired exit code
457

    
458
  """
459
  op = opcodes.OpRemoveNode(node_name=args[0])
460
  SubmitOpCode(op, opts=opts)
461
  return 0
462

    
463

    
464
def PowercycleNode(opts, args):
465
  """Remove a node from the cluster.
466

    
467
  @param opts: the command line options selected by the user
468
  @type args: list
469
  @param args: should contain only one element, the name of
470
      the node to be removed
471
  @rtype: int
472
  @return: the desired exit code
473

    
474
  """
475
  node = args[0]
476
  if (not opts.confirm and
477
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
478
    return 2
479

    
480
  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
481
  result = SubmitOpCode(op, opts=opts)
482
  ToStderr(result)
483
  return 0
484

    
485

    
486
def ListVolumes(opts, args):
487
  """List logical volumes on node(s).
488

    
489
  @param opts: the command line options selected by the user
490
  @type args: list
491
  @param args: should either be an empty list, in which case
492
      we list data for all nodes, or contain a list of nodes
493
      to display data only for those
494
  @rtype: int
495
  @return: the desired exit code
496

    
497
  """
498
  if opts.output is None:
499
    selected_fields = ["node", "phys", "vg",
500
                       "name", "size", "instance"]
501
  else:
502
    selected_fields = opts.output.split(",")
503

    
504
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
505
  output = SubmitOpCode(op, opts=opts)
506

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

    
514
  unitfields = ["size"]
515

    
516
  numfields = ["size"]
517

    
518
  data = GenerateTable(separator=opts.separator, headers=headers,
519
                       fields=selected_fields, unitfields=unitfields,
520
                       numfields=numfields, data=output, units=opts.units)
521

    
522
  for line in data:
523
    ToStdout(line)
524

    
525
  return 0
526

    
527

    
528
def ListStorage(opts, args):
529
  """List physical volumes on node(s).
530

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

    
539
  """
540
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
541
  if opts.user_storage_type is None:
542
    opts.user_storage_type = constants.ST_LVM_PV
543

    
544
  storage_type = ConvertStorageType(opts.user_storage_type)
545

    
546
  if opts.output is None:
547
    selected_fields = _LIST_STOR_DEF_FIELDS
548
  elif opts.output.startswith("+"):
549
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
550
  else:
551
    selected_fields = opts.output.split(",")
552

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

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

    
571
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
572
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
573

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

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

    
589
  for line in data:
590
    ToStdout(line)
591

    
592
  return 0
593

    
594

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

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

    
604
  """
605
  (node_name, user_storage_type, volume_name) = args
606

    
607
  storage_type = ConvertStorageType(user_storage_type)
608

    
609
  changes = {}
610

    
611
  if opts.allocatable is not None:
612
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
613

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

    
623

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

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

    
633
  """
634
  (node_name, user_storage_type, volume_name) = args
635

    
636
  storage_type = ConvertStorageType(user_storage_type)
637

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

    
644

    
645
def SetNodeParams(opts, args):
646
  """Modifies a node.
647

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

    
654
  """
655
  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
656
    ToStderr("Please give at least one of the parameters.")
657
    return 1
658

    
659
  op = opcodes.OpSetNodeParams(node_name=args[0],
660
                               master_candidate=opts.master_candidate,
661
                               offline=opts.offline,
662
                               drained=opts.drained,
663
                               force=opts.force,
664
                               auto_promote=opts.auto_promote)
665

    
666
  # even if here we process the result, we allow submit only
667
  result = SubmitOrSend(op, opts)
668

    
669
  if result:
670
    ToStdout("Modified node %s", args[0])
671
    for param, data in result:
672
      ToStdout(" - %-5s -> %s", param, data)
673
  return 0
674

    
675

    
676
commands = {
677
  'add': (
678
    AddNode, [ArgHost(min=1, max=1)],
679
    [SECONDARY_IP_OPT, READD_OPT, NONODE_SETUP_OPT, VERBOSE_OPT],
680
    "[-s ip] [--readd] [--no-node-setup]  [--verbose] "
681
    " <node_name>",
682
    "Add a node to the cluster"),
683
  'evacuate': (
684
    EvacuateNode, [ArgNode(min=1)],
685
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT],
686
    "[-f] {-I <iallocator> | -n <dst>} <node>",
687
    "Relocate the secondary instances from a node"
688
    " to other nodes (only for instances with drbd disk template)"),
689
  'failover': (
690
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT],
691
    "[-f] <node>",
692
    "Stops the primary instances on a node and start them on their"
693
    " secondary node (only for instances with drbd disk template)"),
694
  'migrate': (
695
    MigrateNode, ARGS_ONE_NODE, [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT],
696
    "[-f] <node>",
697
    "Migrate all the primary instance on a node away from it"
698
    " (only for instances of type drbd)"),
699
  'info': (
700
    ShowNodeConfig, ARGS_MANY_NODES, [],
701
    "[<node_name>...]", "Show information about the node(s)"),
702
  'list': (
703
    ListNodes, ARGS_MANY_NODES,
704
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
705
    "[nodes...]",
706
    "Lists the nodes in the cluster. The available fields are (see the man"
707
    " page for details): %s. The default field list is (in order): %s." %
708
    (utils.CommaJoin(_LIST_HEADERS), utils.CommaJoin(_LIST_DEF_FIELDS))),
709
  'modify': (
710
    SetNodeParams, ARGS_ONE_NODE,
711
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
712
     AUTO_PROMOTE_OPT],
713
    "<node_name>", "Alters the parameters of a node"),
714
  'powercycle': (
715
    PowercycleNode, ARGS_ONE_NODE,
716
    [FORCE_OPT, CONFIRM_OPT],
717
    "<node_name>", "Tries to forcefully powercycle a node"),
718
  'remove': (
719
    RemoveNode, ARGS_ONE_NODE, [],
720
    "<node_name>", "Removes a node from the cluster"),
721
  'volumes': (
722
    ListVolumes, [ArgNode()],
723
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
724
    "[<node_name>...]", "List logical volumes on node(s)"),
725
  'list-storage': (
726
    ListStorage, ARGS_MANY_NODES,
727
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT],
728
    "[<node_name>...]", "List physical volumes on node(s). The available"
729
    " fields are (see the man page for details): %s." %
730
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
731
  'modify-storage': (
732
    ModifyStorage,
733
    [ArgNode(min=1, max=1),
734
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
735
     ArgFile(min=1, max=1)],
736
    [ALLOCATABLE_OPT],
737
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
738
  'repair-storage': (
739
    RepairStorage,
740
    [ArgNode(min=1, max=1),
741
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
742
     ArgFile(min=1, max=1)],
743
    [IGNORE_CONSIST_OPT],
744
    "<node_name> <storage_type> <name>",
745
    "Repairs a storage volume on a node"),
746
  'list-tags': (
747
    ListTags, ARGS_ONE_NODE, [],
748
    "<node_name>", "List the tags of the given node"),
749
  'add-tags': (
750
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
751
    "<node_name> tag...", "Add tags to the given node"),
752
  'remove-tags': (
753
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
754
    "<node_name> tag...", "Remove tags from the given node"),
755
  }
756

    
757

    
758
if __name__ == '__main__':
759
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))