Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_node.py @ a0724772

History | View | Annotate | Download (26.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
NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
104
                              action="store_false", dest="node_setup",
105
                              help=("Do not make initial SSH setup on remote"
106
                                    " node (needs to be done manually)"))
107

    
108

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

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

    
119

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

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

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

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

    
138
  cmd.extend(nodes)
139

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

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

    
147

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

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

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

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

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

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

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

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

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

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

    
206

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

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

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

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

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

    
226

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

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

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

    
240

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

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

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

    
254
  dst_node = opts.dst_node
255
  iallocator = opts.iallocator
256

    
257
  op = opcodes.OpNodeEvacStrategy(nodes=args,
258
                                  iallocator=iallocator,
259
                                  remote_node=dst_node)
260

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

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

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

    
294

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

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

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

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

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

    
319
  pinst = utils.NiceSort(pinst)
320

    
321
  retcode = 0
322

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

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

    
341

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

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

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

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

    
357
  pinst = utils.NiceSort(pinst)
358

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

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

    
375

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

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

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

    
396
  for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
397
       master_capable, vm_capable, powered, ndparams,
398
       ndparams_custom) in result:
399
    ToStdout("Node name: %s", name)
400
    ToStdout("  primary ip: %s", primary_ip)
401
    ToStdout("  secondary ip: %s", secondary_ip)
402
    ToStdout("  master candidate: %s", is_mc)
403
    ToStdout("  drained: %s", drained)
404
    ToStdout("  offline: %s", offline)
405
    if powered is not None:
406
      ToStdout("  powered: %s", powered)
407
    ToStdout("  master_capable: %s", master_capable)
408
    ToStdout("  vm_capable: %s", vm_capable)
409
    if vm_capable:
410
      if pinst:
411
        ToStdout("  primary for instances:")
412
        for iname in utils.NiceSort(pinst):
413
          ToStdout("    - %s", iname)
414
      else:
415
        ToStdout("  primary for no instances")
416
      if sinst:
417
        ToStdout("  secondary for instances:")
418
        for iname in utils.NiceSort(sinst):
419
          ToStdout("    - %s", iname)
420
      else:
421
        ToStdout("  secondary for no instances")
422
    ToStdout("  node parameters:")
423
    buf = StringIO()
424
    FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
425
    ToStdout(buf.getvalue().rstrip("\n"))
426

    
427
  return 0
428

    
429

    
430
def RemoveNode(opts, args):
431
  """Remove a node from the cluster.
432

433
  @param opts: the command line options selected by the user
434
  @type args: list
435
  @param args: should contain only one element, the name of
436
      the node to be removed
437
  @rtype: int
438
  @return: the desired exit code
439

440
  """
441
  op = opcodes.OpNodeRemove(node_name=args[0])
442
  SubmitOpCode(op, opts=opts)
443
  return 0
444

    
445

    
446
def PowercycleNode(opts, args):
447
  """Remove a node from the cluster.
448

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

456
  """
457
  node = args[0]
458
  if (not opts.confirm and
459
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
460
    return 2
461

    
462
  op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
463
  result = SubmitOpCode(op, opts=opts)
464
  if result:
465
    ToStderr(result)
466
  return 0
467

    
468

    
469
def PowerNode(opts, args):
470
  """Change/ask power state of a node.
471

472
  @param opts: the command line options selected by the user
473
  @type args: list
474
  @param args: should contain only one element, the name of
475
      the node to be removed
476
  @rtype: int
477
  @return: the desired exit code
478

479
  """
480
  command = args[0]
481
  node = args[1]
482

    
483
  if command not in _LIST_POWER_COMMANDS:
484
    ToStderr("power subcommand %s not supported." % command)
485
    return constants.EXIT_FAILURE
486

    
487
  oob_command = "power-%s" % command
488

    
489
  opcodelist = []
490
  if oob_command == constants.OOB_POWER_OFF:
491
    opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
492
                                              auto_promote=opts.auto_promote))
493

    
494
  opcodelist.append(opcodes.OpOobCommand(node_names=[node],
495
                                         command=oob_command))
496

    
497
  cli.SetGenericOpcodeOpts(opcodelist, opts)
498

    
499
  job_id = cli.SendJob(opcodelist)
500

    
501
  # We just want the OOB Opcode status
502
  # If it fails PollJob gives us the error message in it
503
  result = cli.PollJob(job_id)[-1]
504

    
505
  if result:
506
    (_, data_tuple) = result[0]
507
    if data_tuple[0] != constants.RS_NORMAL:
508
      if data_tuple[0] == constants.RS_UNAVAIL:
509
        result = "OOB is not supported"
510
      else:
511
        result = "RPC failed, look out for warning in the output"
512
      ToStderr(result)
513
      return constants.EXIT_FAILURE
514
    else:
515
      if oob_command == constants.OOB_POWER_STATUS:
516
        text = "The machine is %spowered"
517
        if data_tuple[1][constants.OOB_POWER_STATUS_POWERED]:
518
          result = text % ""
519
        else:
520
          result = text % "not "
521
        ToStdout(result)
522

    
523
  return constants.EXIT_SUCCESS
524

    
525

    
526
def Health(opts, args):
527
  """Show health of a node using OOB.
528

529
  @param opts: the command line options selected by the user
530
  @type args: list
531
  @param args: should contain only one element, the name of
532
      the node to be removed
533
  @rtype: int
534
  @return: the desired exit code
535

536
  """
537
  op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH)
538
  result = SubmitOpCode(op, opts=opts)
539

    
540
  if opts.no_headers:
541
    headers = None
542
  else:
543
    headers = {"node": "Node", "status": "Status"}
544

    
545
  errs = 0
546
  data = []
547
  for node_result in result:
548
    (node_tuple, data_tuple) = node_result
549
    (_, node_name) = node_tuple
550
    (data_status, data_node) = data_tuple
551
    if data_status == constants.RS_NORMAL:
552
      data.append([node_name, "%s=%s" % tuple(data_node[0])])
553
      for item, status in data_node[1:]:
554
        data.append(["", "%s=%s" % (item, status)])
555
    else:
556
      errs += 1
557
      data.append([node_name, cli.FormatResultError(data_status)])
558

    
559
  data = GenerateTable(separator=opts.separator, headers=headers,
560
                       fields=["node", "status"], data=data)
561

    
562
  for line in data:
563
    ToStdout(line)
564

    
565
  if errs:
566
    return constants.EXIT_FAILURE
567
  else:
568
    return constants.EXIT_SUCCESS
569

    
570

    
571
def ListVolumes(opts, args):
572
  """List logical volumes on node(s).
573

574
  @param opts: the command line options selected by the user
575
  @type args: list
576
  @param args: should either be an empty list, in which case
577
      we list data for all nodes, or contain a list of nodes
578
      to display data only for those
579
  @rtype: int
580
  @return: the desired exit code
581

582
  """
583
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
584

    
585
  op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
586
  output = SubmitOpCode(op, opts=opts)
587

    
588
  if not opts.no_headers:
589
    headers = {"node": "Node", "phys": "PhysDev",
590
               "vg": "VG", "name": "Name",
591
               "size": "Size", "instance": "Instance"}
592
  else:
593
    headers = None
594

    
595
  unitfields = ["size"]
596

    
597
  numfields = ["size"]
598

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

    
603
  for line in data:
604
    ToStdout(line)
605

    
606
  return 0
607

    
608

    
609
def ListStorage(opts, args):
610
  """List physical volumes on node(s).
611

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

620
  """
621
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
622
  if opts.user_storage_type is None:
623
    opts.user_storage_type = constants.ST_LVM_PV
624

    
625
  storage_type = ConvertStorageType(opts.user_storage_type)
626

    
627
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
628

    
629
  op = opcodes.OpNodeQueryStorage(nodes=args,
630
                                  storage_type=storage_type,
631
                                  output_fields=selected_fields)
632
  output = SubmitOpCode(op, opts=opts)
633

    
634
  if not opts.no_headers:
635
    headers = {
636
      constants.SF_NODE: "Node",
637
      constants.SF_TYPE: "Type",
638
      constants.SF_NAME: "Name",
639
      constants.SF_SIZE: "Size",
640
      constants.SF_USED: "Used",
641
      constants.SF_FREE: "Free",
642
      constants.SF_ALLOCATABLE: "Allocatable",
643
      }
644
  else:
645
    headers = None
646

    
647
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
648
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
649

    
650
  # change raw values to nicer strings
651
  for row in output:
652
    for idx, field in enumerate(selected_fields):
653
      val = row[idx]
654
      if field == constants.SF_ALLOCATABLE:
655
        if val:
656
          val = "Y"
657
        else:
658
          val = "N"
659
      row[idx] = str(val)
660

    
661
  data = GenerateTable(separator=opts.separator, headers=headers,
662
                       fields=selected_fields, unitfields=unitfields,
663
                       numfields=numfields, data=output, units=opts.units)
664

    
665
  for line in data:
666
    ToStdout(line)
667

    
668
  return 0
669

    
670

    
671
def ModifyStorage(opts, args):
672
  """Modify storage volume on a node.
673

674
  @param opts: the command line options selected by the user
675
  @type args: list
676
  @param args: should contain 3 items: node name, storage type and volume name
677
  @rtype: int
678
  @return: the desired exit code
679

680
  """
681
  (node_name, user_storage_type, volume_name) = args
682

    
683
  storage_type = ConvertStorageType(user_storage_type)
684

    
685
  changes = {}
686

    
687
  if opts.allocatable is not None:
688
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
689

    
690
  if changes:
691
    op = opcodes.OpNodeModifyStorage(node_name=node_name,
692
                                     storage_type=storage_type,
693
                                     name=volume_name,
694
                                     changes=changes)
695
    SubmitOpCode(op, opts=opts)
696
  else:
697
    ToStderr("No changes to perform, exiting.")
698

    
699

    
700
def RepairStorage(opts, args):
701
  """Repairs a storage volume on a node.
702

703
  @param opts: the command line options selected by the user
704
  @type args: list
705
  @param args: should contain 3 items: node name, storage type and volume name
706
  @rtype: int
707
  @return: the desired exit code
708

709
  """
710
  (node_name, user_storage_type, volume_name) = args
711

    
712
  storage_type = ConvertStorageType(user_storage_type)
713

    
714
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
715
                                   storage_type=storage_type,
716
                                   name=volume_name,
717
                                   ignore_consistency=opts.ignore_consistency)
718
  SubmitOpCode(op, opts=opts)
719

    
720

    
721
def SetNodeParams(opts, args):
722
  """Modifies a node.
723

724
  @param opts: the command line options selected by the user
725
  @type args: list
726
  @param args: should contain only one element, the node name
727
  @rtype: int
728
  @return: the desired exit code
729

730
  """
731
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
732
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
733
                 opts.ndparams]
734
  if all_changes.count(None) == len(all_changes):
735
    ToStderr("Please give at least one of the parameters.")
736
    return 1
737

    
738
  op = opcodes.OpNodeSetParams(node_name=args[0],
739
                               master_candidate=opts.master_candidate,
740
                               offline=opts.offline,
741
                               drained=opts.drained,
742
                               master_capable=opts.master_capable,
743
                               vm_capable=opts.vm_capable,
744
                               secondary_ip=opts.secondary_ip,
745
                               force=opts.force,
746
                               ndparams=opts.ndparams,
747
                               auto_promote=opts.auto_promote,
748
                               powered=opts.node_powered)
749

    
750
  # even if here we process the result, we allow submit only
751
  result = SubmitOrSend(op, opts)
752

    
753
  if result:
754
    ToStdout("Modified node %s", args[0])
755
    for param, data in result:
756
      ToStdout(" - %-5s -> %s", param, data)
757
  return 0
758

    
759

    
760
commands = {
761
  'add': (
762
    AddNode, [ArgHost(min=1, max=1)],
763
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
764
     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
765
     CAPAB_VM_OPT, NODE_PARAMS_OPT],
766
    "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
767
    " <node_name>",
768
    "Add a node to the cluster"),
769
  'evacuate': (
770
    EvacuateNode, [ArgNode(min=1)],
771
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
772
     PRIORITY_OPT],
773
    "[-f] {-I <iallocator> | -n <dst>} <node>",
774
    "Relocate the secondary instances from a node"
775
    " to other nodes (only for instances with drbd disk template)"),
776
  'failover': (
777
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
778
    "[-f] <node>",
779
    "Stops the primary instances on a node and start them on their"
780
    " secondary node (only for instances with drbd disk template)"),
781
  'migrate': (
782
    MigrateNode, ARGS_ONE_NODE,
783
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
784
    "[-f] <node>",
785
    "Migrate all the primary instance on a node away from it"
786
    " (only for instances of type drbd)"),
787
  'info': (
788
    ShowNodeConfig, ARGS_MANY_NODES, [],
789
    "[<node_name>...]", "Show information about the node(s)"),
790
  'list': (
791
    ListNodes, ARGS_MANY_NODES,
792
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
793
    "[nodes...]",
794
    "Lists the nodes in the cluster. The available fields can be shown using"
795
    " the \"list-fields\" command (see the man page for details)."
796
    " The default field list is (in order): %s." %
797
    utils.CommaJoin(_LIST_DEF_FIELDS)),
798
  "list-fields": (
799
    ListNodeFields, [ArgUnknown()],
800
    [NOHDR_OPT, SEP_OPT],
801
    "[fields...]",
802
    "Lists all available fields for nodes"),
803
  'modify': (
804
    SetNodeParams, ARGS_ONE_NODE,
805
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
806
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
807
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
808
     NODE_POWERED_OPT],
809
    "<node_name>", "Alters the parameters of a node"),
810
  'powercycle': (
811
    PowercycleNode, ARGS_ONE_NODE,
812
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
813
    "<node_name>", "Tries to forcefully powercycle a node"),
814
  'power': (
815
    PowerNode,
816
    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
817
     ArgNode(min=1, max=1)],
818
    [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT],
819
    "on|off|cycle|status <node>",
820
    "Change power state of node by calling out-of-band helper."),
821
  'remove': (
822
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
823
    "<node_name>", "Removes a node from the cluster"),
824
  'volumes': (
825
    ListVolumes, [ArgNode()],
826
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
827
    "[<node_name>...]", "List logical volumes on node(s)"),
828
  'list-storage': (
829
    ListStorage, ARGS_MANY_NODES,
830
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
831
     PRIORITY_OPT],
832
    "[<node_name>...]", "List physical volumes on node(s). The available"
833
    " fields are (see the man page for details): %s." %
834
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
835
  'modify-storage': (
836
    ModifyStorage,
837
    [ArgNode(min=1, max=1),
838
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
839
     ArgFile(min=1, max=1)],
840
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
841
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
842
  'repair-storage': (
843
    RepairStorage,
844
    [ArgNode(min=1, max=1),
845
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
846
     ArgFile(min=1, max=1)],
847
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
848
    "<node_name> <storage_type> <name>",
849
    "Repairs a storage volume on a node"),
850
  'list-tags': (
851
    ListTags, ARGS_ONE_NODE, [],
852
    "<node_name>", "List the tags of the given node"),
853
  'add-tags': (
854
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
855
    "<node_name> tag...", "Add tags to the given node"),
856
  'remove-tags': (
857
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
858
    [TAG_SRC_OPT, PRIORITY_OPT],
859
    "<node_name> tag...", "Remove tags from the given node"),
860
  "health": (
861
    Health, ARGS_MANY_NODES,
862
    [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT],
863
    "[<node_name>...]", "List health of node(s) using out-of-band"),
864
  }
865

    
866

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