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

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
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
38
from ganeti import netutils
Iustin Pop's avatar
Iustin Pop committed
39
40


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

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

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


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

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

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


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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

  return 0


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

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

  dst_node = opts.dst_node
  iallocator = opts.iallocator

257
258
259
260
261
262
263
264
265
  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
266
267
    return constants.EXIT_SUCCESS

268
269
270
  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
271
272
    return constants.EXIT_CONFIRMATION

273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
  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
293
294


Iustin Pop's avatar
Iustin Pop committed
295
296
297
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

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

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

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

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


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

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

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

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
368
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
369
    mode = constants.HT_MIGRATION_NONLIVE
370
  else:
371
372
    mode = opts.migration_mode
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
373
  SubmitOpCode(op, cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
374
375


Iustin Pop's avatar
Iustin Pop committed
376
377
378
def ShowNodeConfig(opts, args):
  """Show node information.

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

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

  return 0


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


Iustin Pop's avatar
Iustin Pop committed
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
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)
451
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
452
453
454
455
  ToStderr(result)
  return 0


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

459
460
461
462
463
464
465
466
  @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

467
468
469
470
471
472
473
474
  """
  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)
475
  output = SubmitOpCode(op, opts=opts)
476
477

  if not opts.no_headers:
478
479
480
481
482
483
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

484
  unitfields = ["size"]
485
486
487

  numfields = ["size"]

488
489
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
490
                       numfields=numfields, data=output, units=opts.units)
491
492

  for line in data:
493
    ToStdout(line)
494
495
496
497

  return 0


Iustin Pop's avatar
Iustin Pop committed
498
def ListStorage(opts, args):
499
500
501
502
503
504
505
506
507
508
509
  """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

  """
510
511
512
513
  # 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

514
  storage_type = ConvertStorageType(opts.user_storage_type)
515

516
  if opts.output is None:
517
    selected_fields = _LIST_STOR_DEF_FIELDS
518
  elif opts.output.startswith("+"):
519
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
520
521
522
523
  else:
    selected_fields = opts.output.split(",")

  op = opcodes.OpQueryNodeStorage(nodes=args,
524
                                  storage_type=storage_type,
525
                                  output_fields=selected_fields)
526
  output = SubmitOpCode(op, opts=opts)
527
528
529

  if not opts.no_headers:
    headers = {
530
531
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
532
533
534
535
536
537
538
539
540
541
542
543
      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]

544
545
546
547
548
549
550
551
552
553
554
  # 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)

555
556
557
558
559
560
561
562
563
564
  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
565
def ModifyStorage(opts, args):
566
567
568
569
570
571
572
573
574
575
576
  """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

577
  storage_type = ConvertStorageType(user_storage_type)
578
579
580
581

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
582
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
583
584
585
586
587
588

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
589
    SubmitOpCode(op, opts=opts)
590
591
  else:
    ToStderr("No changes to perform, exiting.")
592
593


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


Iustin Pop's avatar
Iustin Pop committed
615
616
617
618
619
620
621
622
623
624
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

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

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


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