Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_node.py @ f2c6673d

History | View | Annotate | Download (28.7 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
OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
118
                         default=constants.OOB_TIMEOUT,
119
                         help="Maximum time to wait for out-of-band helper")
120

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

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

    
131

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

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

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

    
141
  # Pass --debug|--verbose to the external script if set on our invocation
142
  # --debug overrides --verbose
143
  if options.debug:
144
    cmd.append("--debug")
145
  elif options.verbose:
146
    cmd.append("--verbose")
147
  if not options.ssh_key_check:
148
    cmd.append("--no-ssh-key-check")
149
  if options.force_join:
150
    cmd.append("--force-join")
151

    
152
  cmd.extend(nodes)
153

    
154
  result = utils.RunCmd(cmd, interactive=True)
155

    
156
  if result.failed:
157
    errmsg = ("Command '%s' failed with exit code %s; output %r" %
158
              (result.cmd, result.exit_code, result.output))
159
    raise errors.OpExecError(errmsg)
160

    
161

    
162
@UsesRPC
163
def AddNode(opts, args):
164
  """Add a node to the cluster.
165

166
  @param opts: the command line options selected by the user
167
  @type args: list
168
  @param args: should contain only one element, the new node name
169
  @rtype: int
170
  @return: the desired exit code
171

172
  """
173
  cl = GetClient()
174
  node = netutils.GetHostname(name=args[0]).name
175
  readd = opts.readd
176

    
177
  try:
178
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
179
                           use_locking=False)
180
    node_exists, sip = output[0]
181
  except (errors.OpPrereqError, errors.OpExecError):
182
    node_exists = ""
183
    sip = None
184

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

    
197
  # read the cluster name from the master
198
  output = cl.QueryConfigValues(['cluster_name'])
199
  cluster_name = output[0]
200

    
201
  if not readd and opts.node_setup:
202
    ToStderr("-- WARNING -- \n"
203
             "Performing this operation is going to replace the ssh daemon"
204
             " keypair\n"
205
             "on the target machine (%s) with the ones of the"
206
             " current one\n"
207
             "and grant full intra-cluster ssh root access to/from it\n", node)
208

    
209
  if opts.node_setup:
210
    _RunSetupSSH(opts, [node])
211

    
212
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
213

    
214
  op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
215
                         readd=opts.readd, group=opts.nodegroup,
216
                         vm_capable=opts.vm_capable, ndparams=opts.ndparams,
217
                         master_capable=opts.master_capable)
218
  SubmitOpCode(op, opts=opts)
219

    
220

    
221
def ListNodes(opts, args):
222
  """List nodes and their properties.
223

224
  @param opts: the command line options selected by the user
225
  @type args: list
226
  @param args: nodes to list, or empty for all
227
  @rtype: int
228
  @return: the desired exit code
229

230
  """
231
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
232

    
233
  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
234
                              (",".join, False))
235

    
236
  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
237
                     opts.separator, not opts.no_headers,
238
                     format_override=fmtoverride, verbose=opts.verbose)
239

    
240

    
241
def ListNodeFields(opts, args):
242
  """List node fields.
243

244
  @param opts: the command line options selected by the user
245
  @type args: list
246
  @param args: fields to list, or empty for all
247
  @rtype: int
248
  @return: the desired exit code
249

250
  """
251
  return GenericListFields(constants.QR_NODE, args, opts.separator,
252
                           not opts.no_headers)
253

    
254

    
255
def EvacuateNode(opts, args):
256
  """Relocate all secondary instance from a node.
257

258
  @param opts: the command line options selected by the user
259
  @type args: list
260
  @param args: should be an empty list
261
  @rtype: int
262
  @return: the desired exit code
263

264
  """
265
  cl = GetClient()
266
  force = opts.force
267

    
268
  dst_node = opts.dst_node
269
  iallocator = opts.iallocator
270

    
271
  op = opcodes.OpNodeEvacStrategy(nodes=args,
272
                                  iallocator=iallocator,
273
                                  remote_node=dst_node)
274

    
275
  result = SubmitOpCode(op, cl=cl, opts=opts)
276
  if not result:
277
    # no instances to migrate
278
    ToStderr("No secondary instances on node(s) %s, exiting.",
279
             utils.CommaJoin(args))
280
    return constants.EXIT_SUCCESS
281

    
282
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
283
                               (",".join("'%s'" % name[0] for name in result),
284
                               utils.CommaJoin(args))):
285
    return constants.EXIT_CONFIRMATION
286

    
287
  jex = JobExecutor(cl=cl, opts=opts)
288
  for row in result:
289
    iname = row[0]
290
    node = row[1]
291
    ToStdout("Will relocate instance %s to node %s", iname, node)
292
    op = opcodes.OpInstanceReplaceDisks(instance_name=iname,
293
                                        remote_node=node, disks=[],
294
                                        mode=constants.REPLACE_DISK_CHG,
295
                                        early_release=opts.early_release)
296
    jex.QueueJob(iname, op)
297
  results = jex.GetResults()
298
  bad_cnt = len([row for row in results if not row[0]])
299
  if bad_cnt == 0:
300
    ToStdout("All %d instance(s) failed over successfully.", len(results))
301
    rcode = constants.EXIT_SUCCESS
302
  else:
303
    ToStdout("There were errors during the failover:\n"
304
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
305
    rcode = constants.EXIT_FAILURE
306
  return rcode
307

    
308

    
309
def FailoverNode(opts, args):
310
  """Failover all primary instance on a node.
311

312
  @param opts: the command line options selected by the user
313
  @type args: list
314
  @param args: should be an empty list
315
  @rtype: int
316
  @return: the desired exit code
317

318
  """
319
  cl = GetClient()
320
  force = opts.force
321
  selected_fields = ["name", "pinst_list"]
322

    
323
  # these fields are static data anyway, so it doesn't matter, but
324
  # locking=True should be safer
325
  result = cl.QueryNodes(names=args, fields=selected_fields,
326
                         use_locking=False)
327
  node, pinst = result[0]
328

    
329
  if not pinst:
330
    ToStderr("No primary instances on node %s, exiting.", node)
331
    return 0
332

    
333
  pinst = utils.NiceSort(pinst)
334

    
335
  retcode = 0
336

    
337
  if not force and not AskUser("Fail over instance(s) %s?" %
338
                               (",".join("'%s'" % name for name in pinst))):
339
    return 2
340

    
341
  jex = JobExecutor(cl=cl, opts=opts)
342
  for iname in pinst:
343
    op = opcodes.OpInstanceFailover(instance_name=iname,
344
                                    ignore_consistency=opts.ignore_consistency)
345
    jex.QueueJob(iname, op)
346
  results = jex.GetResults()
347
  bad_cnt = len([row for row in results if not row[0]])
348
  if bad_cnt == 0:
349
    ToStdout("All %d instance(s) failed over successfully.", len(results))
350
  else:
351
    ToStdout("There were errors during the failover:\n"
352
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
353
  return retcode
354

    
355

    
356
def MigrateNode(opts, args):
357
  """Migrate all primary instance on a node.
358

359
  """
360
  cl = GetClient()
361
  force = opts.force
362
  selected_fields = ["name", "pinst_list"]
363

    
364
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
365
  node, pinst = result[0]
366

    
367
  if not pinst:
368
    ToStdout("No primary instances on node %s, exiting." % node)
369
    return 0
370

    
371
  pinst = utils.NiceSort(pinst)
372

    
373
  if not force and not AskUser("Migrate instance(s) %s?" %
374
                               (",".join("'%s'" % name for name in pinst))):
375
    return 2
376

    
377
  # this should be removed once --non-live is deprecated
378
  if not opts.live and opts.migration_mode is not None:
379
    raise errors.OpPrereqError("Only one of the --non-live and "
380
                               "--migration-mode options can be passed",
381
                               errors.ECODE_INVAL)
382
  if not opts.live: # --non-live passed
383
    mode = constants.HT_MIGRATION_NONLIVE
384
  else:
385
    mode = opts.migration_mode
386
  op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode)
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

    
528
  cli.SetGenericOpcodeOpts(opcodelist, opts)
529

    
530
  job_id = cli.SendJob(opcodelist)
531

    
532
  # We just want the OOB Opcode status
533
  # If it fails PollJob gives us the error message in it
534
  result = cli.PollJob(job_id)[-1]
535

    
536
  errs = 0
537
  data = []
538
  for node_result in result:
539
    (node_tuple, data_tuple) = node_result
540
    (_, node_name) = node_tuple
541
    (data_status, data_node) = data_tuple
542
    if data_status == constants.RS_NORMAL:
543
      if oob_command == constants.OOB_POWER_STATUS:
544
        if data_node[constants.OOB_POWER_STATUS_POWERED]:
545
          text = "powered"
546
        else:
547
          text = "unpowered"
548
        data.append([node_name, text])
549
      else:
550
        # We don't expect data here, so we just say, it was successfully invoked
551
        data.append([node_name, "invoked"])
552
    else:
553
      errs += 1
554
      data.append([node_name, cli.FormatResultError(data_status, True)])
555

    
556
  data = GenerateTable(separator=opts.separator, headers=headers,
557
                       fields=["node", "status"], data=data)
558

    
559
  for line in data:
560
    ToStdout(line)
561

    
562
  if errs:
563
    return constants.EXIT_FAILURE
564
  else:
565
    return constants.EXIT_SUCCESS
566

    
567

    
568
def Health(opts, args):
569
  """Show health of a node using OOB.
570

571
  @param opts: the command line options selected by the user
572
  @type args: list
573
  @param args: should contain only one element, the name of
574
      the node to be removed
575
  @rtype: int
576
  @return: the desired exit code
577

578
  """
579
  op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
580
                            timeout=opts.oob_timeout)
581
  result = SubmitOpCode(op, opts=opts)
582

    
583
  if opts.no_headers:
584
    headers = None
585
  else:
586
    headers = {"node": "Node", "status": "Status"}
587

    
588
  errs = 0
589
  data = []
590
  for node_result in result:
591
    (node_tuple, data_tuple) = node_result
592
    (_, node_name) = node_tuple
593
    (data_status, data_node) = data_tuple
594
    if data_status == constants.RS_NORMAL:
595
      data.append([node_name, "%s=%s" % tuple(data_node[0])])
596
      for item, status in data_node[1:]:
597
        data.append(["", "%s=%s" % (item, status)])
598
    else:
599
      errs += 1
600
      data.append([node_name, cli.FormatResultError(data_status, True)])
601

    
602
  data = GenerateTable(separator=opts.separator, headers=headers,
603
                       fields=["node", "status"], data=data)
604

    
605
  for line in data:
606
    ToStdout(line)
607

    
608
  if errs:
609
    return constants.EXIT_FAILURE
610
  else:
611
    return constants.EXIT_SUCCESS
612

    
613

    
614
def ListVolumes(opts, args):
615
  """List logical volumes on node(s).
616

617
  @param opts: the command line options selected by the user
618
  @type args: list
619
  @param args: should either be an empty list, in which case
620
      we list data for all nodes, or contain a list of nodes
621
      to display data only for those
622
  @rtype: int
623
  @return: the desired exit code
624

625
  """
626
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
627

    
628
  op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
629
  output = SubmitOpCode(op, opts=opts)
630

    
631
  if not opts.no_headers:
632
    headers = {"node": "Node", "phys": "PhysDev",
633
               "vg": "VG", "name": "Name",
634
               "size": "Size", "instance": "Instance"}
635
  else:
636
    headers = None
637

    
638
  unitfields = ["size"]
639

    
640
  numfields = ["size"]
641

    
642
  data = GenerateTable(separator=opts.separator, headers=headers,
643
                       fields=selected_fields, unitfields=unitfields,
644
                       numfields=numfields, data=output, units=opts.units)
645

    
646
  for line in data:
647
    ToStdout(line)
648

    
649
  return 0
650

    
651

    
652
def ListStorage(opts, args):
653
  """List physical volumes on node(s).
654

655
  @param opts: the command line options selected by the user
656
  @type args: list
657
  @param args: should either be an empty list, in which case
658
      we list data for all nodes, or contain a list of nodes
659
      to display data only for those
660
  @rtype: int
661
  @return: the desired exit code
662

663
  """
664
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
665
  if opts.user_storage_type is None:
666
    opts.user_storage_type = constants.ST_LVM_PV
667

    
668
  storage_type = ConvertStorageType(opts.user_storage_type)
669

    
670
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
671

    
672
  op = opcodes.OpNodeQueryStorage(nodes=args,
673
                                  storage_type=storage_type,
674
                                  output_fields=selected_fields)
675
  output = SubmitOpCode(op, opts=opts)
676

    
677
  if not opts.no_headers:
678
    headers = {
679
      constants.SF_NODE: "Node",
680
      constants.SF_TYPE: "Type",
681
      constants.SF_NAME: "Name",
682
      constants.SF_SIZE: "Size",
683
      constants.SF_USED: "Used",
684
      constants.SF_FREE: "Free",
685
      constants.SF_ALLOCATABLE: "Allocatable",
686
      }
687
  else:
688
    headers = None
689

    
690
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
691
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
692

    
693
  # change raw values to nicer strings
694
  for row in output:
695
    for idx, field in enumerate(selected_fields):
696
      val = row[idx]
697
      if field == constants.SF_ALLOCATABLE:
698
        if val:
699
          val = "Y"
700
        else:
701
          val = "N"
702
      row[idx] = str(val)
703

    
704
  data = GenerateTable(separator=opts.separator, headers=headers,
705
                       fields=selected_fields, unitfields=unitfields,
706
                       numfields=numfields, data=output, units=opts.units)
707

    
708
  for line in data:
709
    ToStdout(line)
710

    
711
  return 0
712

    
713

    
714
def ModifyStorage(opts, args):
715
  """Modify storage volume on a node.
716

717
  @param opts: the command line options selected by the user
718
  @type args: list
719
  @param args: should contain 3 items: node name, storage type and volume name
720
  @rtype: int
721
  @return: the desired exit code
722

723
  """
724
  (node_name, user_storage_type, volume_name) = args
725

    
726
  storage_type = ConvertStorageType(user_storage_type)
727

    
728
  changes = {}
729

    
730
  if opts.allocatable is not None:
731
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
732

    
733
  if changes:
734
    op = opcodes.OpNodeModifyStorage(node_name=node_name,
735
                                     storage_type=storage_type,
736
                                     name=volume_name,
737
                                     changes=changes)
738
    SubmitOpCode(op, opts=opts)
739
  else:
740
    ToStderr("No changes to perform, exiting.")
741

    
742

    
743
def RepairStorage(opts, args):
744
  """Repairs a storage volume on a node.
745

746
  @param opts: the command line options selected by the user
747
  @type args: list
748
  @param args: should contain 3 items: node name, storage type and volume name
749
  @rtype: int
750
  @return: the desired exit code
751

752
  """
753
  (node_name, user_storage_type, volume_name) = args
754

    
755
  storage_type = ConvertStorageType(user_storage_type)
756

    
757
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
758
                                   storage_type=storage_type,
759
                                   name=volume_name,
760
                                   ignore_consistency=opts.ignore_consistency)
761
  SubmitOpCode(op, opts=opts)
762

    
763

    
764
def SetNodeParams(opts, args):
765
  """Modifies a node.
766

767
  @param opts: the command line options selected by the user
768
  @type args: list
769
  @param args: should contain only one element, the node name
770
  @rtype: int
771
  @return: the desired exit code
772

773
  """
774
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
775
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
776
                 opts.ndparams]
777
  if all_changes.count(None) == len(all_changes):
778
    ToStderr("Please give at least one of the parameters.")
779
    return 1
780

    
781
  op = opcodes.OpNodeSetParams(node_name=args[0],
782
                               master_candidate=opts.master_candidate,
783
                               offline=opts.offline,
784
                               drained=opts.drained,
785
                               master_capable=opts.master_capable,
786
                               vm_capable=opts.vm_capable,
787
                               secondary_ip=opts.secondary_ip,
788
                               force=opts.force,
789
                               ndparams=opts.ndparams,
790
                               auto_promote=opts.auto_promote,
791
                               powered=opts.node_powered)
792

    
793
  # even if here we process the result, we allow submit only
794
  result = SubmitOrSend(op, opts)
795

    
796
  if result:
797
    ToStdout("Modified node %s", args[0])
798
    for param, data in result:
799
      ToStdout(" - %-5s -> %s", param, data)
800
  return 0
801

    
802

    
803
commands = {
804
  'add': (
805
    AddNode, [ArgHost(min=1, max=1)],
806
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
807
     NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
808
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT],
809
    "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
810
    " [--no-node-setup] [--verbose]"
811
    " <node_name>",
812
    "Add a node to the cluster"),
813
  'evacuate': (
814
    EvacuateNode, [ArgNode(min=1)],
815
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
816
     PRIORITY_OPT],
817
    "[-f] {-I <iallocator> | -n <dst>} <node>",
818
    "Relocate the secondary instances from a node"
819
    " to other nodes (only for instances with drbd disk template)"),
820
  'failover': (
821
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
822
    "[-f] <node>",
823
    "Stops the primary instances on a node and start them on their"
824
    " secondary node (only for instances with drbd disk template)"),
825
  'migrate': (
826
    MigrateNode, ARGS_ONE_NODE,
827
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
828
    "[-f] <node>",
829
    "Migrate all the primary instance on a node away from it"
830
    " (only for instances of type drbd)"),
831
  'info': (
832
    ShowNodeConfig, ARGS_MANY_NODES, [],
833
    "[<node_name>...]", "Show information about the node(s)"),
834
  'list': (
835
    ListNodes, ARGS_MANY_NODES,
836
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
837
    "[nodes...]",
838
    "Lists the nodes in the cluster. The available fields can be shown using"
839
    " the \"list-fields\" command (see the man page for details)."
840
    " The default field list is (in order): %s." %
841
    utils.CommaJoin(_LIST_DEF_FIELDS)),
842
  "list-fields": (
843
    ListNodeFields, [ArgUnknown()],
844
    [NOHDR_OPT, SEP_OPT],
845
    "[fields...]",
846
    "Lists all available fields for nodes"),
847
  'modify': (
848
    SetNodeParams, ARGS_ONE_NODE,
849
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
850
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
851
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
852
     NODE_POWERED_OPT],
853
    "<node_name>", "Alters the parameters of a node"),
854
  'powercycle': (
855
    PowercycleNode, ARGS_ONE_NODE,
856
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
857
    "<node_name>", "Tries to forcefully powercycle a node"),
858
  'power': (
859
    PowerNode,
860
    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
861
     ArgNode()],
862
    [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
863
     FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT],
864
    "on|off|cycle|status [nodes...]",
865
    "Change power state of node by calling out-of-band helper."),
866
  'remove': (
867
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
868
    "<node_name>", "Removes a node from the cluster"),
869
  'volumes': (
870
    ListVolumes, [ArgNode()],
871
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
872
    "[<node_name>...]", "List logical volumes on node(s)"),
873
  'list-storage': (
874
    ListStorage, ARGS_MANY_NODES,
875
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
876
     PRIORITY_OPT],
877
    "[<node_name>...]", "List physical volumes on node(s). The available"
878
    " fields are (see the man page for details): %s." %
879
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
880
  'modify-storage': (
881
    ModifyStorage,
882
    [ArgNode(min=1, max=1),
883
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
884
     ArgFile(min=1, max=1)],
885
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
886
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
887
  'repair-storage': (
888
    RepairStorage,
889
    [ArgNode(min=1, max=1),
890
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
891
     ArgFile(min=1, max=1)],
892
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
893
    "<node_name> <storage_type> <name>",
894
    "Repairs a storage volume on a node"),
895
  'list-tags': (
896
    ListTags, ARGS_ONE_NODE, [],
897
    "<node_name>", "List the tags of the given node"),
898
  'add-tags': (
899
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
900
    "<node_name> tag...", "Add tags to the given node"),
901
  'remove-tags': (
902
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
903
    [TAG_SRC_OPT, PRIORITY_OPT],
904
    "<node_name> tag...", "Remove tags from the given node"),
905
  "health": (
906
    Health, ARGS_MANY_NODES,
907
    [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
908
    "[<node_name>...]", "List health of node(s) using out-of-band"),
909
  }
910

    
911

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