gnt_node.py 23 KB
Newer Older
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

Iustin Pop's avatar
Iustin Pop committed
21
"""Node related commands"""
Iustin Pop's avatar
Iustin Pop committed
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)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-node
28

Iustin Pop's avatar
Iustin Pop committed
29
from ganeti.cli import *
30
from ganeti import bootstrap
Iustin Pop's avatar
Iustin Pop committed
31
32
from ganeti import opcodes
from ganeti import utils
33
from ganeti import constants
Iustin Pop's avatar
Iustin Pop committed
34
from ganeti import errors
35
from ganeti import netutils
Iustin Pop's avatar
Iustin Pop committed
36
37


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

45

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


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


Adeodato Simo's avatar
Adeodato Simo committed
62
#: headers (and full field list) for L{ListStorage}
63
64
65
66
67
68
69
70
71
72
73
_LIST_STOR_HEADERS = {
  constants.SF_NODE: "Node",
  constants.SF_TYPE: "Type",
  constants.SF_NAME: "Name",
  constants.SF_SIZE: "Size",
  constants.SF_USED: "Used",
  constants.SF_FREE: "Free",
  constants.SF_ALLOCATABLE: "Allocatable",
  }


74
75
76
77
78
79
80
#: User-facing storage unit types
_USER_STORAGE_TYPE = {
  constants.ST_FILE: "file",
  constants.ST_LVM_PV: "lvm-pv",
  constants.ST_LVM_VG: "lvm-vg",
  }

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

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

_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()

Michael Hanselmann's avatar
Michael Hanselmann committed
96

97
98
99
100
101
102
NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
                              action="store_false", dest="node_setup",
                              help=("Do not make initial SSH setup on remote"
                                    " node (needs to be done manually)"))


103
104
105
106
107
108
109
def ConvertStorageType(user_storage_type):
  """Converts a user storage type to its internal name.

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


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def _RunSetupSSH(options, nodes):
  """Wrapper around utils.RunCmd to call setup-ssh

  @param options: The command line options
  @param nodes: The nodes to setup

  """
  cmd = [constants.SETUP_SSH]

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

  cmd.extend(nodes)

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

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


142
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
143
def AddNode(opts, args):
144
145
146
147
148
149
150
  """Add a node to the cluster.

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

  """
Iustin Pop's avatar
Iustin Pop committed
153
  cl = GetClient()
154
  node = netutils.GetHostname(name=args[0]).name
Iustin Pop's avatar
Iustin Pop committed
155
156
157
158
  readd = opts.readd

  try:
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
159
                           use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
160
161
162
163
164
165
166
167
168
169
170
171
    node_exists, sip = output[0]
  except (errors.OpPrereqError, errors.OpExecError):
    node_exists = ""
    sip = None

  if readd:
    if not node_exists:
      ToStderr("Node %s not in the cluster"
               " - please retry without '--readd'", node)
      return 1
  else:
    if node_exists:
172
      ToStderr("Node %s already in the cluster (as %s)"
Iustin Pop's avatar
Iustin Pop committed
173
               " - please retry with '--readd'", node, node_exists)
174
      return 1
Iustin Pop's avatar
Iustin Pop committed
175
    sip = opts.secondary_ip
176

Iustin Pop's avatar
Iustin Pop committed
177
178
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
179
  cluster_name = output[0]
Iustin Pop's avatar
Iustin Pop committed
180

181
  if not readd and opts.node_setup:
Iustin Pop's avatar
Iustin Pop committed
182
183
184
185
186
187
    ToStderr("-- WARNING -- \n"
             "Performing this operation is going to replace the ssh daemon"
             " keypair\n"
             "on the target machine (%s) with the ones of the"
             " current one\n"
             "and grant full intra-cluster ssh root access to/from it\n", node)
188

189
190
  if opts.node_setup:
    _RunSetupSSH(opts, [node])
191

192
193
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)

Iustin Pop's avatar
Iustin Pop committed
194
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
195
                         readd=opts.readd, group=opts.nodegroup,
196
                         vm_capable=opts.vm_capable, ndparams=opts.ndparams,
197
                         master_capable=opts.master_capable)
198
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
199
200
201
202
203


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

204
205
  @param opts: the command line options selected by the user
  @type args: list
Adeodato Simo's avatar
Adeodato Simo committed
206
  @param args: nodes to list, or empty for all
207
208
209
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
210
  """
211
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
212

213
  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
214
                              (",".join, False))
Iustin Pop's avatar
Iustin Pop committed
215

216
217
218
  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
                     opts.separator, not opts.no_headers,
                     format_override=fmtoverride)
219
220


221
222
def ListNodeFields(opts, args):
  """List node fields.
223

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

230
231
232
  """
  return GenericListFields(constants.QR_NODE, args, opts.separator,
                           not opts.no_headers)
Iustin Pop's avatar
Iustin Pop committed
233
234


Iustin Pop's avatar
Iustin Pop committed
235
236
237
def EvacuateNode(opts, args):
  """Relocate all secondary instance from a node.

238
239
240
241
242
243
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
244
  """
245
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
246
  force = opts.force
247
248
249
250

  dst_node = opts.dst_node
  iallocator = opts.iallocator

251
252
253
254
255
256
257
258
259
  op = opcodes.OpNodeEvacuationStrategy(nodes=args,
                                        iallocator=iallocator,
                                        remote_node=dst_node)

  result = SubmitOpCode(op, cl=cl, opts=opts)
  if not result:
    # no instances to migrate
    ToStderr("No secondary instances on node(s) %s, exiting.",
             utils.CommaJoin(args))
Iustin Pop's avatar
Iustin Pop committed
260
261
    return constants.EXIT_SUCCESS

262
263
264
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
                               (",".join("'%s'" % name[0] for name in result),
                               utils.CommaJoin(args))):
Iustin Pop's avatar
Iustin Pop committed
265
266
    return constants.EXIT_CONFIRMATION

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


Iustin Pop's avatar
Iustin Pop committed
289
290
291
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

292
293
294
295
296
297
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
298
  """
299
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
300
301
302
  force = opts.force
  selected_fields = ["name", "pinst_list"]

303
304
305
  # these fields are static data anyway, so it doesn't matter, but
  # locking=True should be safer
  result = cl.QueryNodes(names=args, fields=selected_fields,
306
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
307
308
309
  node, pinst = result[0]

  if not pinst:
310
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
311
312
313
314
315
316
317
318
319
320
    return 0

  pinst = utils.NiceSort(pinst)

  retcode = 0

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

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


Iustin Pop's avatar
Iustin Pop committed
336
337
338
339
340
341
342
343
def MigrateNode(opts, args):
  """Migrate all primary instance on a node.

  """
  cl = GetClient()
  force = opts.force
  selected_fields = ["name", "pinst_list"]

344
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
345
346
347
348
349
350
351
352
353
354
355
356
  node, pinst = result[0]

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

  pinst = utils.NiceSort(pinst)

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

357
  # this should be removed once --non-live is deprecated
358
  if not opts.live and opts.migration_mode is not None:
359
    raise errors.OpPrereqError("Only one of the --non-live and "
360
                               "--migration-mode options can be passed",
361
362
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
363
    mode = constants.HT_MIGRATION_NONLIVE
364
  else:
365
366
    mode = opts.migration_mode
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
367
  SubmitOpCode(op, cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
368
369


Iustin Pop's avatar
Iustin Pop committed
370
371
372
def ShowNodeConfig(opts, args):
  """Show node information.

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

Iustin Pop's avatar
Iustin Pop committed
381
  """
382
383
  cl = GetClient()
  result = cl.QueryNodes(fields=["name", "pip", "sip",
384
                                 "pinst_list", "sinst_list",
385
386
                                 "master_candidate", "drained", "offline",
                                 "master_capable", "vm_capable"],
387
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
388

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

  return 0


def RemoveNode(opts, args):
417
418
419
420
421
422
423
424
425
426
  """Remove a node from the cluster.

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

  """
Iustin Pop's avatar
Iustin Pop committed
427
  op = opcodes.OpRemoveNode(node_name=args[0])
428
  SubmitOpCode(op, opts=opts)
429
  return 0
Iustin Pop's avatar
Iustin Pop committed
430
431


Iustin Pop's avatar
Iustin Pop committed
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def PowercycleNode(opts, args):
  """Remove a node from the cluster.

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

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

  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
449
  result = SubmitOpCode(op, opts=opts)
450
451
  if result:
    ToStderr(result)
Iustin Pop's avatar
Iustin Pop committed
452
453
454
  return 0


455
456
457
def ListVolumes(opts, args):
  """List logical volumes on node(s).

458
459
460
461
462
463
464
465
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we list data for all nodes, or contain a list of nodes
      to display data only for those
  @rtype: int
  @return: the desired exit code

466
  """
467
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
468
469

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
470
  output = SubmitOpCode(op, opts=opts)
471
472

  if not opts.no_headers:
473
474
475
476
477
478
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

479
  unitfields = ["size"]
480
481
482

  numfields = ["size"]

483
484
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
485
                       numfields=numfields, data=output, units=opts.units)
486
487

  for line in data:
488
    ToStdout(line)
489
490
491
492

  return 0


Iustin Pop's avatar
Iustin Pop committed
493
def ListStorage(opts, args):
494
495
496
497
498
499
500
501
502
503
504
  """List physical volumes on node(s).

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

  """
505
506
507
508
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
  if opts.user_storage_type is None:
    opts.user_storage_type = constants.ST_LVM_PV

509
  storage_type = ConvertStorageType(opts.user_storage_type)
510

511
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
512
513

  op = opcodes.OpQueryNodeStorage(nodes=args,
514
                                  storage_type=storage_type,
515
                                  output_fields=selected_fields)
516
  output = SubmitOpCode(op, opts=opts)
517
518
519

  if not opts.no_headers:
    headers = {
520
521
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
522
523
524
525
526
527
528
529
530
531
532
533
      constants.SF_NAME: "Name",
      constants.SF_SIZE: "Size",
      constants.SF_USED: "Used",
      constants.SF_FREE: "Free",
      constants.SF_ALLOCATABLE: "Allocatable",
      }
  else:
    headers = None

  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]

534
535
536
537
538
539
540
541
542
543
544
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == constants.SF_ALLOCATABLE:
        if val:
          val = "Y"
        else:
          val = "N"
      row[idx] = str(val)

545
546
547
548
549
550
551
552
553
554
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
                       numfields=numfields, data=output, units=opts.units)

  for line in data:
    ToStdout(line)

  return 0


Iustin Pop's avatar
Iustin Pop committed
555
def ModifyStorage(opts, args):
556
557
558
559
560
561
562
563
564
565
566
  """Modify storage volume on a node.

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

  """
  (node_name, user_storage_type, volume_name) = args

567
  storage_type = ConvertStorageType(user_storage_type)
568
569
570
571

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
572
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
573
574
575
576
577
578

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
579
    SubmitOpCode(op, opts=opts)
580
581
  else:
    ToStderr("No changes to perform, exiting.")
582
583


Iustin Pop's avatar
Iustin Pop committed
584
def RepairStorage(opts, args):
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
  """Repairs a storage volume on a node.

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

  """
  (node_name, user_storage_type, volume_name) = args

  storage_type = ConvertStorageType(user_storage_type)

  op = opcodes.OpRepairNodeStorage(node_name=node_name,
                                   storage_type=storage_type,
600
601
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
602
  SubmitOpCode(op, opts=opts)
603
604


Iustin Pop's avatar
Iustin Pop committed
605
606
607
608
609
610
611
612
613
614
def SetNodeParams(opts, args):
  """Modifies a node.

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

  """
Iustin Pop's avatar
Iustin Pop committed
615
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
616
617
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
                 opts.ndparams]
Iustin Pop's avatar
Iustin Pop committed
618
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
619
620
621
622
    ToStderr("Please give at least one of the parameters.")
    return 1

  op = opcodes.OpSetNodeParams(node_name=args[0],
Iustin Pop's avatar
Iustin Pop committed
623
624
625
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
626
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
627
                               vm_capable=opts.vm_capable,
628
                               secondary_ip=opts.secondary_ip,
629
                               force=opts.force,
630
                               ndparams=opts.ndparams,
631
                               auto_promote=opts.auto_promote)
Iustin Pop's avatar
Iustin Pop committed
632
633
634
635
636
637
638
639
640
641
642

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

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


Iustin Pop's avatar
Iustin Pop committed
643
commands = {
644
645
  'add': (
    AddNode, [ArgHost(min=1, max=1)],
646
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
647
     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
648
     CAPAB_VM_OPT, NODE_PARAMS_OPT],
649
    "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
650
    " <node_name>",
651
652
    "Add a node to the cluster"),
  'evacuate': (
653
    EvacuateNode, [ArgNode(min=1)],
654
655
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
     PRIORITY_OPT],
656
657
658
659
    "[-f] {-I <iallocator> | -n <dst>} <node>",
    "Relocate the secondary instances from a node"
    " to other nodes (only for instances with drbd disk template)"),
  'failover': (
660
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
661
662
663
664
    "[-f] <node>",
    "Stops the primary instances on a node and start them on their"
    " secondary node (only for instances with drbd disk template)"),
  'migrate': (
665
666
    MigrateNode, ARGS_ONE_NODE,
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
667
668
669
670
    "[-f] <node>",
    "Migrate all the primary instance on a node away from it"
    " (only for instances of type drbd)"),
  'info': (
671
    ShowNodeConfig, ARGS_MANY_NODES, [],
672
673
674
    "[<node_name>...]", "Show information about the node(s)"),
  'list': (
    ListNodes, ARGS_MANY_NODES,
675
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
676
    "[nodes...]",
677
678
679
680
681
682
683
684
685
    "Lists the nodes in the cluster. The available fields can be shown using"
    " the \"list-fields\" command (see the man page for details)."
    " The default field list is (in order): %s." %
    utils.CommaJoin(_LIST_DEF_FIELDS)),
  "list-fields": (
    ListNodeFields, [ArgUnknown()],
    [NOHDR_OPT, SEP_OPT],
    "[fields...]",
    "Lists all available fields for nodes"),
686
687
  'modify': (
    SetNodeParams, ARGS_ONE_NODE,
Iustin Pop's avatar
Iustin Pop committed
688
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
689
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
690
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT],
691
692
693
    "<node_name>", "Alters the parameters of a node"),
  'powercycle': (
    PowercycleNode, ARGS_ONE_NODE,
694
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
695
696
    "<node_name>", "Tries to forcefully powercycle a node"),
  'remove': (
697
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
698
699
700
    "<node_name>", "Removes a node from the cluster"),
  'volumes': (
    ListVolumes, [ArgNode()],
701
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
702
    "[<node_name>...]", "List logical volumes on node(s)"),
Iustin Pop's avatar
Iustin Pop committed
703
704
  'list-storage': (
    ListStorage, ARGS_MANY_NODES,
705
706
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
     PRIORITY_OPT],
707
708
    "[<node_name>...]", "List physical volumes on node(s). The available"
    " fields are (see the man page for details): %s." %
709
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
Iustin Pop's avatar
Iustin Pop committed
710
711
  'modify-storage': (
    ModifyStorage,
712
713
714
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
715
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
716
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
Iustin Pop's avatar
Iustin Pop committed
717
718
  'repair-storage': (
    RepairStorage,
719
720
721
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
722
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
723
724
725
    "<node_name> <storage_type> <name>",
    "Repairs a storage volume on a node"),
  'list-tags': (
726
    ListTags, ARGS_ONE_NODE, [],
727
728
    "<node_name>", "List the tags of the given node"),
  'add-tags': (
729
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
730
731
    "<node_name> tag...", "Add tags to the given node"),
  'remove-tags': (
732
733
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
    [TAG_SRC_OPT, PRIORITY_OPT],
734
    "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
735
736
737
  }


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