gnt-node 21.7 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
import sys

from ganeti.cli import *
32
from ganeti import cli
Iustin Pop's avatar
Iustin Pop committed
33
34
from ganeti import opcodes
from ganeti import utils
35
from ganeti import constants
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(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)
Iustin Pop's avatar
Iustin Pop committed
177
178
179
180
181
182
  SubmitOpCode(op)


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
227
      elif val is None:
        val = "?"
      row[idx] = str(val)
228

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

  return 0


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

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

  dst_node = opts.dst_node
  iallocator = opts.iallocator

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

Iustin Pop's avatar
Iustin Pop committed
259
  selected_fields = ["name", "sinst_list"]
260
  src_node = args[0]
Iustin Pop's avatar
Iustin Pop committed
261

262
  result = cl.QueryNodes(names=[src_node], fields=selected_fields,
263
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
264
265
266
  src_node, sinst = result[0]

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

270
  if dst_node is not None:
271
272
    result = cl.QueryNodes(names=[dst_node], fields=["name"],
                           use_locking=False)
273
274
275
276
277
    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)" %
278
                                 src_node, errors.ECODE_INVAL)
279
280
281
282
    txt_msg = "to node %s" % dst_node
  else:
    txt_msg = "using iallocator %s" % iallocator

Iustin Pop's avatar
Iustin Pop committed
283
284
285
  sinst = utils.NiceSort(sinst)

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

291
292
293
  op = opcodes.OpEvacuateNode(node_name=args[0], remote_node=dst_node,
                              iallocator=iallocator)
  SubmitOpCode(op, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
294
295


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

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

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

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

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


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

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

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

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


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

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

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

  return 0


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


Iustin Pop's avatar
Iustin Pop committed
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
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


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

451
452
453
454
455
456
457
458
  @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

459
460
461
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)
  output = SubmitOpCode(op)

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

476
  unitfields = ["size"]
477
478
479

  numfields = ["size"]

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

  for line in data:
485
    ToStdout(line)
486
487
488
489

  return 0


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

  """
502
503
504
505
  # 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

506
  storage_type = ConvertStorageType(opts.user_storage_type)
507

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

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

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

536
537
538
539
540
541
542
543
544
545
546
  # 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)

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

569
  storage_type = ConvertStorageType(user_storage_type)
570
571
572
573
574
575
576
577
578
579
580
581

  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)
582
583
  else:
    ToStderr("No changes to perform, exiting.")
584
585


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


Iustin Pop's avatar
Iustin Pop committed
607
608
609
610
611
612
613
614
615
616
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

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

621
622
623
624
625
626
627
628
  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
629
630
631
632
633

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

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


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