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

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

Iustin Pop's avatar
Iustin Pop committed
21
"""Node related commands"""
Iustin Pop's avatar
Iustin Pop committed
22

23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-node
28

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


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

45

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


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


62
63
64
65
#: default list of power commands
_LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]


Adeodato Simo's avatar
Adeodato Simo committed
66
#: headers (and full field list) for L{ListStorage}
67
68
69
70
71
72
73
74
75
76
77
_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",
  }


78
79
80
81
82
83
84
#: User-facing storage unit types
_USER_STORAGE_TYPE = {
  constants.ST_FILE: "file",
  constants.ST_LVM_PV: "lvm-pv",
  constants.ST_LVM_VG: "lvm-vg",
  }

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

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

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


107
108
109
110
111
112
113
def ConvertStorageType(user_storage_type):
  """Converts a user storage type to its internal name.

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


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def _RunSetupSSH(options, nodes):
  """Wrapper around utils.RunCmd to call setup-ssh

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

  """
  cmd = [constants.SETUP_SSH]

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

  cmd.extend(nodes)

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

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


146
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
147
def AddNode(opts, args):
148
149
150
151
152
153
154
  """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
155
156

  """
Iustin Pop's avatar
Iustin Pop committed
157
  cl = GetClient()
158
  node = netutils.GetHostname(name=args[0]).name
Iustin Pop's avatar
Iustin Pop committed
159
160
161
162
  readd = opts.readd

  try:
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
163
                           use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
164
165
166
167
168
169
170
171
172
173
174
175
    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:
176
      ToStderr("Node %s already in the cluster (as %s)"
Iustin Pop's avatar
Iustin Pop committed
177
               " - please retry with '--readd'", node, node_exists)
178
      return 1
Iustin Pop's avatar
Iustin Pop committed
179
    sip = opts.secondary_ip
180

Iustin Pop's avatar
Iustin Pop committed
181
182
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
183
  cluster_name = output[0]
Iustin Pop's avatar
Iustin Pop committed
184

185
  if not readd and opts.node_setup:
Iustin Pop's avatar
Iustin Pop committed
186
187
188
189
190
191
    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)
192

193
194
  if opts.node_setup:
    _RunSetupSSH(opts, [node])
195

196
197
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)

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


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

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

Iustin Pop's avatar
Iustin Pop committed
214
  """
215
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
216

217
  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
218
                              (",".join, False))
Iustin Pop's avatar
Iustin Pop committed
219

220
221
222
  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
                     opts.separator, not opts.no_headers,
                     format_override=fmtoverride)
223
224


225
226
def ListNodeFields(opts, args):
  """List node fields.
227

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

234
235
236
  """
  return GenericListFields(constants.QR_NODE, args, opts.separator,
                           not opts.no_headers)
Iustin Pop's avatar
Iustin Pop committed
237
238


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

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

  dst_node = opts.dst_node
  iallocator = opts.iallocator

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

266
267
268
  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
269
270
    return constants.EXIT_CONFIRMATION

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


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

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

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

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

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


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

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

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

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


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

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

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

  return 0


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


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


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
def PowerNode(opts, args):
  """Change/ask power state of a node.

  @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

  """
  command = args[0]
  node = args[1]

  if command not in _LIST_POWER_COMMANDS:
    ToStderr("power subcommand %s not supported." % command)
    return constants.EXIT_FAILURE

  oob_command = "power-%s" % command

479
  op = opcodes.OpOobCommand(node_name=node, command=oob_command)
480
481
482
483
484
485
486
487
488
489
490
491
492
  result = SubmitOpCode(op, opts=opts)
  if result:
    if oob_command == constants.OOB_POWER_STATUS:
      text = "The machine is %spowered"
      if result[constants.OOB_POWER_STATUS_POWERED]:
        result = text % ""
      else:
        result = text % "not "
    ToStderr(result)

  return constants.EXIT_SUCCESS


493
494
495
def ListVolumes(opts, args):
  """List logical volumes on node(s).

496
497
498
499
500
501
502
503
  @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

504
  """
505
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
506
507

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
508
  output = SubmitOpCode(op, opts=opts)
509
510

  if not opts.no_headers:
511
512
513
514
515
516
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

517
  unitfields = ["size"]
518
519
520

  numfields = ["size"]

521
522
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
523
                       numfields=numfields, data=output, units=opts.units)
524
525

  for line in data:
526
    ToStdout(line)
527
528
529
530

  return 0


Iustin Pop's avatar
Iustin Pop committed
531
def ListStorage(opts, args):
532
533
534
535
536
537
538
539
540
541
542
  """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

  """
543
544
545
546
  # 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

547
  storage_type = ConvertStorageType(opts.user_storage_type)
548

549
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
550
551

  op = opcodes.OpQueryNodeStorage(nodes=args,
552
                                  storage_type=storage_type,
553
                                  output_fields=selected_fields)
554
  output = SubmitOpCode(op, opts=opts)
555
556
557

  if not opts.no_headers:
    headers = {
558
559
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
560
561
562
563
564
565
566
567
568
569
570
571
      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]

572
573
574
575
576
577
578
579
580
581
582
  # 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)

583
584
585
586
587
588
589
590
591
592
  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
593
def ModifyStorage(opts, args):
594
595
596
597
598
599
600
601
602
603
604
  """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

605
  storage_type = ConvertStorageType(user_storage_type)
606
607
608
609

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
610
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
611
612
613
614
615
616

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
617
    SubmitOpCode(op, opts=opts)
618
619
  else:
    ToStderr("No changes to perform, exiting.")
620
621


Iustin Pop's avatar
Iustin Pop committed
622
def RepairStorage(opts, args):
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
  """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,
638
639
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
640
  SubmitOpCode(op, opts=opts)
641
642


Iustin Pop's avatar
Iustin Pop committed
643
644
645
646
647
648
649
650
651
652
def SetNodeParams(opts, args):
  """Modifies a node.

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

  """
Iustin Pop's avatar
Iustin Pop committed
653
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
654
655
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
                 opts.ndparams]
Iustin Pop's avatar
Iustin Pop committed
656
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
657
658
659
660
    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
661
662
663
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
664
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
665
                               vm_capable=opts.vm_capable,
666
                               secondary_ip=opts.secondary_ip,
667
                               force=opts.force,
668
                               ndparams=opts.ndparams,
669
                               auto_promote=opts.auto_promote)
Iustin Pop's avatar
Iustin Pop committed
670
671
672
673
674
675
676
677
678
679
680

  # 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
681
commands = {
682
683
  'add': (
    AddNode, [ArgHost(min=1, max=1)],
684
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
685
     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
686
     CAPAB_VM_OPT, NODE_PARAMS_OPT],
687
    "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
688
    " <node_name>",
689
690
    "Add a node to the cluster"),
  'evacuate': (
691
    EvacuateNode, [ArgNode(min=1)],
692
693
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
     PRIORITY_OPT],
694
695
696
697
    "[-f] {-I <iallocator> | -n <dst>} <node>",
    "Relocate the secondary instances from a node"
    " to other nodes (only for instances with drbd disk template)"),
  'failover': (
698
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
699
700
701
702
    "[-f] <node>",
    "Stops the primary instances on a node and start them on their"
    " secondary node (only for instances with drbd disk template)"),
  'migrate': (
703
704
    MigrateNode, ARGS_ONE_NODE,
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
705
706
707
708
    "[-f] <node>",
    "Migrate all the primary instance on a node away from it"
    " (only for instances of type drbd)"),
  'info': (
709
    ShowNodeConfig, ARGS_MANY_NODES, [],
710
711
712
    "[<node_name>...]", "Show information about the node(s)"),
  'list': (
    ListNodes, ARGS_MANY_NODES,
713
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
714
    "[nodes...]",
715
716
717
718
719
720
721
722
723
    "Lists the nodes in the cluster. The available fields can be shown using"
    " the \"list-fields\" command (see the man page for details)."
    " The default field list is (in order): %s." %
    utils.CommaJoin(_LIST_DEF_FIELDS)),
  "list-fields": (
    ListNodeFields, [ArgUnknown()],
    [NOHDR_OPT, SEP_OPT],
    "[fields...]",
    "Lists all available fields for nodes"),
724
725
  'modify': (
    SetNodeParams, ARGS_ONE_NODE,
Iustin Pop's avatar
Iustin Pop committed
726
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
727
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
728
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT],
729
730
731
    "<node_name>", "Alters the parameters of a node"),
  'powercycle': (
    PowercycleNode, ARGS_ONE_NODE,
732
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
733
    "<node_name>", "Tries to forcefully powercycle a node"),
734
735
736
737
738
739
  'power': (
    PowerNode,
    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
     ArgNode(min=1, max=1)],
    [], "on|off|cycle|status <node>",
    "Change power state of node by calling out-of-band helper."),
740
  'remove': (
741
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
742
743
744
    "<node_name>", "Removes a node from the cluster"),
  'volumes': (
    ListVolumes, [ArgNode()],
745
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
746
    "[<node_name>...]", "List logical volumes on node(s)"),
Iustin Pop's avatar
Iustin Pop committed
747
748
  'list-storage': (
    ListStorage, ARGS_MANY_NODES,
749
750
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
     PRIORITY_OPT],
751
752
    "[<node_name>...]", "List physical volumes on node(s). The available"
    " fields are (see the man page for details): %s." %
753
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
Iustin Pop's avatar
Iustin Pop committed
754
755
  'modify-storage': (
    ModifyStorage,
756
757
758
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
759
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
760
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
Iustin Pop's avatar
Iustin Pop committed
761
762
  'repair-storage': (
    RepairStorage,
763
764
765
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
766
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
767
768
769
    "<node_name> <storage_type> <name>",
    "Repairs a storage volume on a node"),
  'list-tags': (
770
    ListTags, ARGS_ONE_NODE, [],
771
772
    "<node_name>", "List the tags of the given node"),
  'add-tags': (
773
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
774
775
    "<node_name> tag...", "Add tags to the given node"),
  'remove-tags': (
776
777
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
    [TAG_SRC_OPT, PRIORITY_OPT],
778
    "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
779
780
781
  }


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