Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_node.py @ 2afd577f

History | View | Annotate | Download (28.8 kB)

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})