gnt-node 21.5 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
#!/usr/bin/python
#

4
# Copyright (C) 2006, 2007, 2008 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
21
#
# 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.


22
23
24
25
# pylint: disable-msg=W0401,W0614
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

Iustin Pop's avatar
Iustin Pop committed
26
27
28
import sys

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


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

44
45
46
47
48
49
50
51
52
53
54
55
56

#: 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,
  ]


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

75
76
77
78
79
80
81
82
83
84
85
86
87

#: headers (and full field list for L{ListStorage}
_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",
  }


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

95
_STORAGE_TYPE_OPT = \
96
  cli_option("-t", "--storage-type",
97
98
99
100
             dest="user_storage_type",
             choices=_USER_STORAGE_TYPE.keys(),
             default=None,
             metavar="STORAGE_TYPE",
Guido Trotter's avatar
Guido Trotter committed
101
             help=("Storage type (%s)" % " ,".join(_USER_STORAGE_TYPE.keys())))
102
103
104
105
106
107
108

_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
109

110
111
112
113
114
115
116
def ConvertStorageType(user_storage_type):
  """Converts a user storage type to its internal name.

  """
  try:
    return _USER_STORAGE_TYPE[user_storage_type]
  except KeyError:
117
118
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
                               errors.ECODE_INVAL)
119
120


121
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
122
def AddNode(opts, args):
123
124
125
126
127
128
129
  """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
130
131

  """
Iustin Pop's avatar
Iustin Pop committed
132
  cl = GetClient()
133
  dns_data = utils.GetHostInfo(args[0])
134
  node = dns_data.name
Iustin Pop's avatar
Iustin Pop committed
135
136
137
138
  readd = opts.readd

  try:
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
139
                           use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
140
141
142
143
144
145
146
147
148
149
150
151
    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:
152
      ToStderr("Node %s already in the cluster (as %s)"
Iustin Pop's avatar
Iustin Pop committed
153
               " - please retry with '--readd'", node, node_exists)
154
      return 1
Iustin Pop's avatar
Iustin Pop committed
155
    sip = opts.secondary_ip
156

Iustin Pop's avatar
Iustin Pop committed
157
158
159
160
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
  cluster_name = output[0]

Iustin Pop's avatar
Iustin Pop committed
161
  if not readd:
Iustin Pop's avatar
Iustin Pop committed
162
163
164
165
166
167
    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)
168

Iustin Pop's avatar
Iustin Pop committed
169
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
170

Iustin Pop's avatar
Iustin Pop committed
171
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
172
                         readd=opts.readd)
Iustin Pop's avatar
Iustin Pop committed
173
174
175
176
177
178
  SubmitOpCode(op)


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

179
180
181
182
183
184
  @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
185
186
  """
  if opts.output is None:
187
188
189
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
190
191
192
  else:
    selected_fields = opts.output.split(",")

Iustin Pop's avatar
Iustin Pop committed
193
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
194
195

  if not opts.no_headers:
196
    headers = _LIST_HEADERS
197
198
199
  else:
    headers = None

200
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
201

202
203
  numfields = ["dtotal", "dfree",
               "mtotal", "mnode", "mfree",
204
               "pinst_cnt", "sinst_cnt",
205
               "ctotal", "serial_no"]
206

207
  list_type_fields = ("pinst_list", "sinst_list", "tags")
208
209
210
211
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
212
      if field in list_type_fields:
213
        val = ",".join(val)
214
      elif field in ('master', 'master_candidate', 'offline', 'drained'):
215
216
217
218
        if val:
          val = 'Y'
        else:
          val = 'N'
219
220
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
221
222
223
      elif val is None:
        val = "?"
      row[idx] = str(val)
224

225
226
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
227
                       numfields=numfields, data=output, units=opts.units)
228
  for line in data:
229
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
230
231
232
233

  return 0


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

237
238
239
240
241
242
  @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
243
  """
244
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
245
  force = opts.force
246
247
248
249
250
251

  dst_node = opts.dst_node
  iallocator = opts.iallocator

  cnt = [dst_node, iallocator].count(None)
  if cnt != 1:
Iustin Pop's avatar
Iustin Pop committed
252
    raise errors.OpPrereqError("One and only one of the -n and -I"
253
                               " options must be passed", errors.ECODE_INVAL)
254

Iustin Pop's avatar
Iustin Pop committed
255
  selected_fields = ["name", "sinst_list"]
256
  src_node = args[0]
Iustin Pop's avatar
Iustin Pop committed
257

258
  result = cl.QueryNodes(names=[src_node], fields=selected_fields,
259
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
260
261
262
  src_node, sinst = result[0]

  if not sinst:
263
    ToStderr("No secondary instances on node %s, exiting.", src_node)
Iustin Pop's avatar
Iustin Pop committed
264
265
    return constants.EXIT_SUCCESS

266
  if dst_node is not None:
267
268
    result = cl.QueryNodes(names=[dst_node], fields=["name"],
                           use_locking=False)
269
270
271
272
273
    dst_node = result[0][0]

    if src_node == dst_node:
      raise errors.OpPrereqError("Evacuate node needs different source and"
                                 " target nodes (node %s given twice)" %
274
                                 src_node, errors.ECODE_INVAL)
275
276
277
278
    txt_msg = "to node %s" % dst_node
  else:
    txt_msg = "using iallocator %s" % iallocator

Iustin Pop's avatar
Iustin Pop committed
279
280
281
  sinst = utils.NiceSort(sinst)

  if not force and not AskUser("Relocate instance(s) %s from node\n"
282
                               " %s %s?" %
Iustin Pop's avatar
Iustin Pop committed
283
                               (",".join("'%s'" % name for name in sinst),
284
                               src_node, txt_msg)):
Iustin Pop's avatar
Iustin Pop committed
285
286
    return constants.EXIT_CONFIRMATION

287
288
289
  op = opcodes.OpEvacuateNode(node_name=args[0], remote_node=dst_node,
                              iallocator=iallocator)
  SubmitOpCode(op, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
290
291


Iustin Pop's avatar
Iustin Pop committed
292
293
294
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

295
296
297
298
299
300
  @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
301
  """
302
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
303
304
305
  force = opts.force
  selected_fields = ["name", "pinst_list"]

306
307
308
  # 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,
309
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
310
311
312
  node, pinst = result[0]

  if not pinst:
313
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
314
315
316
317
318
319
320
321
322
323
    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

324
  jex = JobExecutor(cl=cl)
Iustin Pop's avatar
Iustin Pop committed
325
326
327
  for iname in pinst:
    op = opcodes.OpFailoverInstance(instance_name=iname,
                                    ignore_consistency=opts.ignore_consistency)
328
329
330
331
332
    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
333
  else:
334
    ToStdout("There were errors during the failover:\n"
335
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
Iustin Pop's avatar
Iustin Pop committed
336
337
338
  return retcode


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

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

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

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

  pinst = utils.NiceSort(pinst)

  retcode = 0

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

362
363
  op = opcodes.OpMigrateNode(node_name=args[0], live=opts.live)
  SubmitOpCode(op, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
364
365


Iustin Pop's avatar
Iustin Pop committed
366
367
368
def ShowNodeConfig(opts, args):
  """Show node information.

369
370
371
372
373
374
375
376
  @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
377
  """
378
379
  cl = GetClient()
  result = cl.QueryNodes(fields=["name", "pip", "sip",
380
381
                                 "pinst_list", "sinst_list",
                                 "master_candidate", "drained", "offline"],
382
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
383

384
385
  for (name, primary_ip, secondary_ip, pinst, sinst,
       is_mc, drained, offline) in result:
386
387
388
    ToStdout("Node name: %s", name)
    ToStdout("  primary ip: %s", primary_ip)
    ToStdout("  secondary ip: %s", secondary_ip)
389
390
391
    ToStdout("  master candidate: %s", is_mc)
    ToStdout("  drained: %s", drained)
    ToStdout("  offline: %s", offline)
Iustin Pop's avatar
Iustin Pop committed
392
    if pinst:
393
      ToStdout("  primary for instances:")
394
      for iname in utils.NiceSort(pinst):
395
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
396
    else:
397
      ToStdout("  primary for no instances")
Iustin Pop's avatar
Iustin Pop committed
398
    if sinst:
399
      ToStdout("  secondary for instances:")
400
      for iname in utils.NiceSort(sinst):
401
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
402
    else:
403
      ToStdout("  secondary for no instances")
Iustin Pop's avatar
Iustin Pop committed
404
405
406
407
408

  return 0


def RemoveNode(opts, args):
409
410
411
412
413
414
415
416
417
418
  """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
419
420
  op = opcodes.OpRemoveNode(node_name=args[0])
  SubmitOpCode(op)
421
  return 0
Iustin Pop's avatar
Iustin Pop committed
422
423


Iustin Pop's avatar
Iustin Pop committed
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
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)
  result = SubmitOpCode(op)
  ToStderr(result)
  return 0


446
447
448
def ListVolumes(opts, args):
  """List logical volumes on node(s).

449
450
451
452
453
454
455
456
  @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

457
458
459
460
461
462
463
464
465
466
467
  """
  if opts.output is None:
    selected_fields = ["node", "phys", "vg",
                       "name", "size", "instance"]
  else:
    selected_fields = opts.output.split(",")

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
  output = SubmitOpCode(op)

  if not opts.no_headers:
468
469
470
471
472
473
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

474
  unitfields = ["size"]
475
476
477

  numfields = ["size"]

478
479
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
480
                       numfields=numfields, data=output, units=opts.units)
481
482

  for line in data:
483
    ToStdout(line)
484
485
486
487

  return 0


Iustin Pop's avatar
Iustin Pop committed
488
def ListStorage(opts, args):
489
490
491
492
493
494
495
496
497
498
499
  """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

  """
500
501
502
503
  # 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

504
  storage_type = ConvertStorageType(opts.user_storage_type)
505

506
  if opts.output is None:
507
    selected_fields = _LIST_STOR_DEF_FIELDS
508
  elif opts.output.startswith("+"):
509
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
510
511
512
513
  else:
    selected_fields = opts.output.split(",")

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

  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
572
573
574
575
576
577
578
579

  changes = {}

  if opts.allocatable is not None:
    changes[constants.SF_ALLOCATABLE] = (opts.allocatable == "yes")

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
    SubmitOpCode(op)
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
603
604
  SubmitOpCode(op)


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

  """
615
  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
Iustin Pop's avatar
Iustin Pop committed
616
617
618
    ToStderr("Please give at least one of the parameters.")
    return 1

619
620
621
622
623
624
625
626
  if opts.master_candidate is not None:
    candidate = opts.master_candidate == 'yes'
  else:
    candidate = None
  if opts.offline is not None:
    offline = opts.offline == 'yes'
  else:
    offline = None
627
628
629
630
631

  if opts.drained is not None:
    drained = opts.drained == 'yes'
  else:
    drained = None
Iustin Pop's avatar
Iustin Pop committed
632
  op = opcodes.OpSetNodeParams(node_name=args[0],
633
634
                               master_candidate=candidate,
                               offline=offline,
635
                               drained=drained,
636
                               force=opts.force)
Iustin Pop's avatar
Iustin Pop committed
637
638
639
640
641
642
643
644
645
646
647

  # 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
648
commands = {
649
650
  'add': (
    AddNode, [ArgHost(min=1, max=1)],
651
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT],
652
653
654
655
    "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
    "Add a node to the cluster"),
  'evacuate': (
    EvacuateNode, ARGS_ONE_NODE,
656
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT],
657
658
659
660
    "[-f] {-I <iallocator> | -n <dst>} <node>",
    "Relocate the secondary instances from a node"
    " to other nodes (only for instances with drbd disk template)"),
  'failover': (
661
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT],
662
663
664
665
    "[-f] <node>",
    "Stops the primary instances on a node and start them on their"
    " secondary node (only for instances with drbd disk template)"),
  'migrate': (
666
    MigrateNode, ARGS_ONE_NODE, [FORCE_OPT, NONLIVE_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, SYNC_OPT],
676
677
678
679
680
681
    "[nodes...]",
    "Lists the nodes in the cluster. The available fields are (see the man"
    " page for details): %s. The default field list is (in order): %s." %
    (", ".join(_LIST_HEADERS), ", ".join(_LIST_DEF_FIELDS))),
  'modify': (
    SetNodeParams, ARGS_ONE_NODE,
682
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT],
683
684
685
    "<node_name>", "Alters the parameters of a node"),
  'powercycle': (
    PowercycleNode, ARGS_ONE_NODE,
686
    [FORCE_OPT, CONFIRM_OPT],
687
688
    "<node_name>", "Tries to forcefully powercycle a node"),
  'remove': (
689
    RemoveNode, ARGS_ONE_NODE, [],
690
691
692
    "<node_name>", "Removes a node from the cluster"),
  'volumes': (
    ListVolumes, [ArgNode()],
693
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
694
    "[<node_name>...]", "List logical volumes on node(s)"),
Iustin Pop's avatar
Iustin Pop committed
695
696
  'list-storage': (
    ListStorage, ARGS_MANY_NODES,
697
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT],
698
699
700
    "[<node_name>...]", "List physical volumes on node(s). The available"
    " fields are (see the man page for details): %s." %
    (", ".join(_LIST_STOR_HEADERS))),
Iustin Pop's avatar
Iustin Pop committed
701
702
  'modify-storage': (
    ModifyStorage,
703
704
705
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
Iustin Pop's avatar
Iustin Pop committed
706
    [ALLOCATABLE_OPT],
707
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
Iustin Pop's avatar
Iustin Pop committed
708
709
  'repair-storage': (
    RepairStorage,
710
711
712
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
713
    [IGNORE_CONSIST_OPT],
714
715
716
    "<node_name> <storage_type> <name>",
    "Repairs a storage volume on a node"),
  'list-tags': (
717
    ListTags, ARGS_ONE_NODE, [],
718
719
    "<node_name>", "List the tags of the given node"),
  'add-tags': (
720
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
721
722
    "<node_name> tag...", "Add tags to the given node"),
  'remove-tags': (
723
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
724
    "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
725
726
727
728
  }


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