gnt-node 21.9 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
#
# 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
30
31
32
33
import sys

from ganeti.cli import *
from ganeti import opcodes
from ganeti import utils
34
from ganeti import constants
35
from ganeti import compat
Iustin Pop's avatar
Iustin Pop committed
36
from ganeti import errors
37
from ganeti import bootstrap
Iustin Pop's avatar
Iustin Pop committed
38
39


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

47
48
49
50
51
52
53
54
55
56
57
58
59

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


60
61
62
63
64
65
66
67
#: 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",
68
  "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
69
70
71
72
  "tags": "Tags",
  "serial_no": "SerialNo",
  "master_candidate": "MasterC",
  "master": "IsMaster",
73
  "offline": "Offline", "drained": "Drained",
Iustin Pop's avatar
Iustin Pop committed
74
  "role": "Role",
75
  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID"
76
77
  }

78
79
80
81
82
83
84
85
86
87
88
89
90

#: 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",
  }


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

98
_STORAGE_TYPE_OPT = \
99
  cli_option("-t", "--storage-type",
100
101
102
103
             dest="user_storage_type",
             choices=_USER_STORAGE_TYPE.keys(),
             default=None,
             metavar="STORAGE_TYPE",
104
105
             help=("Storage type (%s)" %
                   utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
106
107
108
109
110
111
112

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

114
115
116
117
118
119
120
def ConvertStorageType(user_storage_type):
  """Converts a user storage type to its internal name.

  """
  try:
    return _USER_STORAGE_TYPE[user_storage_type]
  except KeyError:
121
122
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
                               errors.ECODE_INVAL)
123
124


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

  """
Iustin Pop's avatar
Iustin Pop committed
136
  cl = GetClient()
137
  dns_data = utils.GetHostInfo(utils.HostInfo.NormalizeName(args[0]))
138
  node = dns_data.name
Iustin Pop's avatar
Iustin Pop committed
139
140
141
142
  readd = opts.readd

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

Iustin Pop's avatar
Iustin Pop committed
161
162
163
164
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
  cluster_name = output[0]

Iustin Pop's avatar
Iustin Pop committed
165
  if not readd:
Iustin Pop's avatar
Iustin Pop committed
166
167
168
169
170
171
    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)
172

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

Iustin Pop's avatar
Iustin Pop committed
175
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
176
                         readd=opts.readd)
177
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
178
179
180
181
182


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

183
184
185
186
187
188
  @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
189
190
  """
  if opts.output is None:
191
192
193
    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
194
195
196
  else:
    selected_fields = opts.output.split(",")

Iustin Pop's avatar
Iustin Pop committed
197
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
198
199

  if not opts.no_headers:
200
    headers = _LIST_HEADERS
201
202
203
  else:
    headers = None

204
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
205

206
207
  numfields = ["dtotal", "dfree",
               "mtotal", "mnode", "mfree",
208
               "pinst_cnt", "sinst_cnt",
209
               "ctotal", "serial_no"]
210

211
  list_type_fields = ("pinst_list", "sinst_list", "tags")
212
213
214
215
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
216
      if field in list_type_fields:
217
        val = ",".join(val)
218
      elif field in ('master', 'master_candidate', 'offline', 'drained'):
219
220
221
222
        if val:
          val = 'Y'
        else:
          val = 'N'
223
224
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
225
226
      elif val is None:
        val = "?"
227
      elif opts.roman_integers and isinstance(val, int):
228
        val = compat.TryToRoman(val)
229
      row[idx] = str(val)
230

231
232
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
233
                       numfields=numfields, data=output, units=opts.units)
234
  for line in data:
235
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
236
237
238
239

  return 0


Iustin Pop's avatar
Iustin Pop committed
240
241
242
def EvacuateNode(opts, args):
  """Relocate all secondary instance from a node.

243
244
245
246
247
248
  @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
249
  """
250
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
251
  force = opts.force
252
253
254
255
256
257

  dst_node = opts.dst_node
  iallocator = opts.iallocator

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

261
262
263
264
265
266
267
268
269
  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
270
271
    return constants.EXIT_SUCCESS

272
273
274
  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
275
276
    return constants.EXIT_CONFIRMATION

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  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
297
298


Iustin Pop's avatar
Iustin Pop committed
299
300
301
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

302
303
304
305
306
307
  @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
308
  """
309
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
310
311
312
  force = opts.force
  selected_fields = ["name", "pinst_list"]

313
314
315
  # 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,
316
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
317
318
319
  node, pinst = result[0]

  if not pinst:
320
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
321
322
323
324
325
326
327
328
329
330
    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

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


Iustin Pop's avatar
Iustin Pop committed
346
347
348
349
350
351
352
353
def MigrateNode(opts, args):
  """Migrate all primary instance on a node.

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

354
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
355
356
357
358
359
360
361
362
363
364
365
366
  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

367
  op = opcodes.OpMigrateNode(node_name=args[0], live=opts.live)
368
  SubmitOpCode(op, cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
369
370


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

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

389
390
  for (name, primary_ip, secondary_ip, pinst, sinst,
       is_mc, drained, offline) 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)
Iustin Pop's avatar
Iustin Pop committed
397
    if pinst:
398
      ToStdout("  primary for instances:")
399
      for iname in utils.NiceSort(pinst):
400
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
401
    else:
402
      ToStdout("  primary for no instances")
Iustin Pop's avatar
Iustin Pop committed
403
    if sinst:
404
      ToStdout("  secondary for instances:")
405
      for iname in utils.NiceSort(sinst):
406
        ToStdout("    - %s", iname)
Iustin Pop's avatar
Iustin Pop committed
407
    else:
408
      ToStdout("  secondary for no instances")
Iustin Pop's avatar
Iustin Pop committed
409
410
411
412
413

  return 0


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


Iustin Pop's avatar
Iustin Pop committed
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)
446
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
447
448
449
450
  ToStderr(result)
  return 0


451
452
453
def ListVolumes(opts, args):
  """List logical volumes on node(s).

454
455
456
457
458
459
460
461
  @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

462
463
464
465
466
467
468
469
  """
  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)
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
  if opts.output is None:
512
    selected_fields = _LIST_STOR_DEF_FIELDS
513
  elif opts.output.startswith("+"):
514
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
515
516
517
518
  else:
    selected_fields = opts.output.split(",")

  op = opcodes.OpQueryNodeStorage(nodes=args,
519
                                  storage_type=storage_type,
520
                                  output_fields=selected_fields)
521
  output = SubmitOpCode(op, opts=opts)
522
523
524

  if not opts.no_headers:
    headers = {
525
526
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
527
528
529
530
531
532
533
534
535
536
537
538
      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]

539
540
541
542
543
544
545
546
547
548
549
  # 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)

550
551
552
553
554
555
556
557
558
559
  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
560
def ModifyStorage(opts, args):
561
562
563
564
565
566
567
568
569
570
571
  """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

572
  storage_type = ConvertStorageType(user_storage_type)
573
574
575
576

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
577
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
578
579
580
581
582
583

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
584
    SubmitOpCode(op, opts=opts)
585
586
  else:
    ToStderr("No changes to perform, exiting.")
587
588


Iustin Pop's avatar
Iustin Pop committed
589
def RepairStorage(opts, args):
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
  """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,
605
606
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
607
  SubmitOpCode(op, opts=opts)
608
609


Iustin Pop's avatar
Iustin Pop committed
610
611
612
613
614
615
616
617
618
619
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

  """
620
  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
Iustin Pop's avatar
Iustin Pop committed
621
622
623
624
    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
625
626
627
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
628
629
                               force=opts.force,
                               auto_promote=opts.auto_promote)
Iustin Pop's avatar
Iustin Pop committed
630
631
632
633
634
635
636
637
638
639
640

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


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