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

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 cli
31
from ganeti import bootstrap
Iustin Pop's avatar
Iustin Pop committed
32
33
from ganeti import opcodes
from ganeti import utils
34
from ganeti import constants
Iustin Pop's avatar
Iustin Pop committed
35
from ganeti import errors
36
from ganeti import netutils
37
from cStringIO import StringIO
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
#: Default field list for L{ListVolumes}
_LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]


52
53
54
55
56
57
58
59
60
61
62
63
#: 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,
  ]


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


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


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

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

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

103
104
105
106
107
108
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)"))


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

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


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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")
135
  if not options.ssh_key_check:
136
    cmd.append("--no-ssh-key-check")
137
138
  if options.force_join:
    cmd.append("--force-join")
139
140
141
142
143
144
145
146
147
148
149

  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)


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

  """
Iustin Pop's avatar
Iustin Pop committed
161
  cl = GetClient()
162
  node = netutils.GetHostname(name=args[0]).name
Iustin Pop's avatar
Iustin Pop committed
163
164
165
  readd = opts.readd

  try:
166
    output = cl.QueryNodes(names=[node], fields=['name', 'sip', 'master'],
167
                           use_locking=False)
168
    node_exists, sip, is_master = output[0]
Iustin Pop's avatar
Iustin Pop committed
169
170
171
172
173
174
175
176
177
  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
178
179
180
    if is_master:
      ToStderr("Node %s is the master, cannot readd", node)
      return 1
Iustin Pop's avatar
Iustin Pop committed
181
182
  else:
    if node_exists:
183
      ToStderr("Node %s already in the cluster (as %s)"
Iustin Pop's avatar
Iustin Pop committed
184
               " - please retry with '--readd'", node, node_exists)
185
      return 1
Iustin Pop's avatar
Iustin Pop committed
186
    sip = opts.secondary_ip
187

Iustin Pop's avatar
Iustin Pop committed
188
189
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
190
  cluster_name = output[0]
Iustin Pop's avatar
Iustin Pop committed
191

192
  if not readd and opts.node_setup:
Iustin Pop's avatar
Iustin Pop committed
193
194
195
196
197
198
    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)
199

200
201
  if opts.node_setup:
    _RunSetupSSH(opts, [node])
202

203
204
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)

Iustin Pop's avatar
Iustin Pop committed
205
  op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
206
                         readd=opts.readd, group=opts.nodegroup,
207
                         vm_capable=opts.vm_capable, ndparams=opts.ndparams,
208
                         master_capable=opts.master_capable)
209
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
210
211
212
213
214


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

215
216
  @param opts: the command line options selected by the user
  @type args: list
Adeodato Simo's avatar
Adeodato Simo committed
217
  @param args: nodes to list, or empty for all
218
219
220
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
221
  """
222
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
223

224
  fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
225
                              (",".join, False))
Iustin Pop's avatar
Iustin Pop committed
226

227
228
  return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
                     opts.separator, not opts.no_headers,
229
                     format_override=fmtoverride, verbose=opts.verbose)
230
231


232
233
def ListNodeFields(opts, args):
  """List node fields.
234

235
236
237
238
239
  @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
240

241
242
243
  """
  return GenericListFields(constants.QR_NODE, args, opts.separator,
                           not opts.no_headers)
Iustin Pop's avatar
Iustin Pop committed
244
245


Iustin Pop's avatar
Iustin Pop committed
246
247
248
def EvacuateNode(opts, args):
  """Relocate all secondary instance from a node.

249
250
251
252
253
254
  @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
255
  """
256
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
257
  force = opts.force
258
259
260
261

  dst_node = opts.dst_node
  iallocator = opts.iallocator

262
263
264
  op = opcodes.OpNodeEvacStrategy(nodes=args,
                                  iallocator=iallocator,
                                  remote_node=dst_node)
265
266
267
268
269
270

  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
271
272
    return constants.EXIT_SUCCESS

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

278
279
280
281
282
  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)
283
284
285
286
    op = opcodes.OpInstanceReplaceDisks(instance_name=iname,
                                        remote_node=node, disks=[],
                                        mode=constants.REPLACE_DISK_CHG,
                                        early_release=opts.early_release)
287
288
289
290
291
292
293
294
295
296
297
    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
298
299


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

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

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

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

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


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

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

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

368
  # this should be removed once --non-live is deprecated
369
  if not opts.live and opts.migration_mode is not None:
370
    raise errors.OpPrereqError("Only one of the --non-live and "
371
                               "--migration-mode options can be passed",
372
373
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
374
    mode = constants.HT_MIGRATION_NONLIVE
375
  else:
376
    mode = opts.migration_mode
377
  op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode)
378
  SubmitOpCode(op, cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
379
380


Iustin Pop's avatar
Iustin Pop committed
381
382
383
def ShowNodeConfig(opts, args):
  """Show node information.

384
385
386
387
388
389
390
391
  @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
392
  """
393
394
  cl = GetClient()
  result = cl.QueryNodes(fields=["name", "pip", "sip",
395
                                 "pinst_list", "sinst_list",
396
                                 "master_candidate", "drained", "offline",
397
398
                                 "master_capable", "vm_capable", "powered",
                                 "ndparams", "custom_ndparams"],
399
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
400

401
402
403
  for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
       master_capable, vm_capable, powered, ndparams,
       ndparams_custom) in result:
404
405
406
    ToStdout("Node name: %s", name)
    ToStdout("  primary ip: %s", primary_ip)
    ToStdout("  secondary ip: %s", secondary_ip)
407
408
409
    ToStdout("  master candidate: %s", is_mc)
    ToStdout("  drained: %s", drained)
    ToStdout("  offline: %s", offline)
410
411
    if powered is not None:
      ToStdout("  powered: %s", powered)
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
    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")
427
428
429
430
    ToStdout("  node parameters:")
    buf = StringIO()
    FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
    ToStdout(buf.getvalue().rstrip("\n"))
Iustin Pop's avatar
Iustin Pop committed
431
432
433
434
435

  return 0


def RemoveNode(opts, args):
436
437
438
439
440
441
442
443
444
445
  """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

  """
446
  op = opcodes.OpNodeRemove(node_name=args[0])
447
  SubmitOpCode(op, opts=opts)
448
  return 0
Iustin Pop's avatar
Iustin Pop committed
449
450


Iustin Pop's avatar
Iustin Pop committed
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
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

467
  op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
468
  result = SubmitOpCode(op, opts=opts)
469
470
  if result:
    ToStderr(result)
Iustin Pop's avatar
Iustin Pop committed
471
472
473
  return 0


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
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

494
495
  opcodelist = []
  if oob_command == constants.OOB_POWER_OFF:
496
    opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
497
498
                                              auto_promote=opts.auto_promote))

499
500
  opcodelist.append(opcodes.OpOobCommand(node_names=[node],
                                         command=oob_command))
501
502
503
504
505
506
507
508
509

  cli.SetGenericOpcodeOpts(opcodelist, opts)

  job_id = cli.SendJob(opcodelist)

  # We just want the OOB Opcode status
  # If it fails PollJob gives us the error message in it
  result = cli.PollJob(job_id)[-1]

510
  if result:
511
512
513
514
    (_, data_tuple) = result[0]
    if data_tuple[0] != constants.RS_NORMAL:
      if data_tuple[0] == constants.RS_UNAVAIL:
        result = "OOB is not supported"
515
      else:
516
517
518
519
520
521
522
523
524
525
526
        result = "RPC failed, look out for warning in the output"
      ToStderr(result)
      return constants.EXIT_FAILURE
    else:
      if oob_command == constants.OOB_POWER_STATUS:
        text = "The machine is %spowered"
        if data_tuple[1][constants.OOB_POWER_STATUS_POWERED]:
          result = text % ""
        else:
          result = text % "not "
        ToStdout(result)
527
528
529
530

  return constants.EXIT_SUCCESS


531
532
533
def ListVolumes(opts, args):
  """List logical volumes on node(s).

534
535
536
537
538
539
540
541
  @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

542
  """
543
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
544

545
  op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
546
  output = SubmitOpCode(op, opts=opts)
547
548

  if not opts.no_headers:
549
550
551
552
553
554
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

555
  unitfields = ["size"]
556
557
558

  numfields = ["size"]

559
560
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
561
                       numfields=numfields, data=output, units=opts.units)
562
563

  for line in data:
564
    ToStdout(line)
565
566
567
568

  return 0


Iustin Pop's avatar
Iustin Pop committed
569
def ListStorage(opts, args):
570
571
572
573
574
575
576
577
578
579
580
  """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

  """
581
582
583
584
  # 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

585
  storage_type = ConvertStorageType(opts.user_storage_type)
586

587
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
588

589
  op = opcodes.OpNodeQueryStorage(nodes=args,
590
                                  storage_type=storage_type,
591
                                  output_fields=selected_fields)
592
  output = SubmitOpCode(op, opts=opts)
593
594
595

  if not opts.no_headers:
    headers = {
596
597
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
598
599
600
601
602
603
604
605
606
607
608
609
      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]

610
611
612
613
614
615
616
617
618
619
620
  # 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)

621
622
623
624
625
626
627
628
629
630
  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
631
def ModifyStorage(opts, args):
632
633
634
635
636
637
638
639
640
641
642
  """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

643
  storage_type = ConvertStorageType(user_storage_type)
644
645
646
647

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
648
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
649
650

  if changes:
651
    op = opcodes.OpNodeModifyStorage(node_name=node_name,
652
653
654
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
655
    SubmitOpCode(op, opts=opts)
656
657
  else:
    ToStderr("No changes to perform, exiting.")
658
659


Iustin Pop's avatar
Iustin Pop committed
660
def RepairStorage(opts, args):
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
  """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,
676
677
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
678
  SubmitOpCode(op, opts=opts)
679
680


Iustin Pop's avatar
Iustin Pop committed
681
682
683
684
685
686
687
688
689
690
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
691
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
692
693
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
                 opts.ndparams]
Iustin Pop's avatar
Iustin Pop committed
694
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
695
696
697
    ToStderr("Please give at least one of the parameters.")
    return 1

698
  op = opcodes.OpNodeSetParams(node_name=args[0],
Iustin Pop's avatar
Iustin Pop committed
699
700
701
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
702
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
703
                               vm_capable=opts.vm_capable,
704
                               secondary_ip=opts.secondary_ip,
705
                               force=opts.force,
706
                               ndparams=opts.ndparams,
707
708
                               auto_promote=opts.auto_promote,
                               powered=opts.node_powered)
Iustin Pop's avatar
Iustin Pop committed
709
710
711
712
713
714
715
716
717
718
719

  # 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
720
commands = {
721
722
  'add': (
    AddNode, [ArgHost(min=1, max=1)],
723
724
725
726
727
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
     NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT],
    "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
    " [--no-node-setup] [--verbose]"
728
    " <node_name>",
729
730
    "Add a node to the cluster"),
  'evacuate': (
731
    EvacuateNode, [ArgNode(min=1)],
732
733
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
     PRIORITY_OPT],
734
735
736
737
    "[-f] {-I <iallocator> | -n <dst>} <node>",
    "Relocate the secondary instances from a node"
    " to other nodes (only for instances with drbd disk template)"),
  'failover': (
738
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
739
740
741
742
    "[-f] <node>",
    "Stops the primary instances on a node and start them on their"
    " secondary node (only for instances with drbd disk template)"),
  'migrate': (
743
744
    MigrateNode, ARGS_ONE_NODE,
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
745
746
747
748
    "[-f] <node>",
    "Migrate all the primary instance on a node away from it"
    " (only for instances of type drbd)"),
  'info': (
749
    ShowNodeConfig, ARGS_MANY_NODES, [],
750
751
752
    "[<node_name>...]", "Show information about the node(s)"),
  'list': (
    ListNodes, ARGS_MANY_NODES,
753
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
754
    "[nodes...]",
755
756
757
758
759
760
761
762
763
    "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"),
764
765
  'modify': (
    SetNodeParams, ARGS_ONE_NODE,
Iustin Pop's avatar
Iustin Pop committed
766
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
767
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
768
769
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
     NODE_POWERED_OPT],
770
771
772
    "<node_name>", "Alters the parameters of a node"),
  'powercycle': (
    PowercycleNode, ARGS_ONE_NODE,
773
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
774
    "<node_name>", "Tries to forcefully powercycle a node"),
775
776
777
778
  'power': (
    PowerNode,
    [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
     ArgNode(min=1, max=1)],
779
780
    [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT],
    "on|off|cycle|status <node>",
781
    "Change power state of node by calling out-of-band helper."),
782
  'remove': (
783
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
784
785
786
    "<node_name>", "Removes a node from the cluster"),
  'volumes': (
    ListVolumes, [ArgNode()],
787
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
788
    "[<node_name>...]", "List logical volumes on node(s)"),
Iustin Pop's avatar
Iustin Pop committed
789
790
  'list-storage': (
    ListStorage, ARGS_MANY_NODES,
791
792
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
     PRIORITY_OPT],
793
794
    "[<node_name>...]", "List physical volumes on node(s). The available"
    " fields are (see the man page for details): %s." %
795
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
Iustin Pop's avatar
Iustin Pop committed
796
797
  'modify-storage': (
    ModifyStorage,
798
799
800
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
801
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
802
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
Iustin Pop's avatar
Iustin Pop committed
803
804
  'repair-storage': (
    RepairStorage,
805
806
807
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
808
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
809
810
811
    "<node_name> <storage_type> <name>",
    "Repairs a storage volume on a node"),
  'list-tags': (
812
    ListTags, ARGS_ONE_NODE, [],
813
814
    "<node_name>", "List the tags of the given node"),
  'add-tags': (
815
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
816
817
    "<node_name> tag...", "Add tags to the given node"),
  'remove-tags': (
818
819
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
    [TAG_SRC_OPT, PRIORITY_OPT],
820
    "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
821
822
823
  }


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