gnt_node.py 25.5 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 139 140 141 142 143 144 145 146 147

  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)


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

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

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

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

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

195 196
  if opts.node_setup:
    _RunSetupSSH(opts, [node])
197

198 199
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)

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


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

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

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

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

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


227 228
def ListNodeFields(opts, args):
  """List node fields.
229

230 231 232 233 234
  @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
235

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


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
  op = opcodes.OpNodeEvacStrategy(nodes=args,
                                  iallocator=iallocator,
                                  remote_node=dst_node)
260 261 262 263 264 265

  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
  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)
278 279 280 281
    op = opcodes.OpInstanceReplaceDisks(instance_name=iname,
                                        remote_node=node, disks=[],
                                        mode=constants.REPLACE_DISK_CHG,
                                        early_release=opts.early_release)
282 283 284 285 286 287 288 289 290 291 292
    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
  for iname in pinst:
329
    op = opcodes.OpInstanceFailover(instance_name=iname,
Iustin Pop's avatar
Iustin Pop committed
330
                                    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
    mode = opts.migration_mode
372
  op = opcodes.OpNodeMigrate(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
                                 "pinst_list", "sinst_list",
391
                                 "master_candidate", "drained", "offline",
392 393
                                 "master_capable", "vm_capable", "powered",
                                 "ndparams", "custom_ndparams"],
394
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
395

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

  return 0


def RemoveNode(opts, args):
431 432 433 434 435 436 437 438 439 440
  """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

  """
441
  op = opcodes.OpNodeRemove(node_name=args[0])
442
  SubmitOpCode(op, opts=opts)
443
  return 0
Iustin Pop's avatar
Iustin Pop committed
444 445


Iustin Pop's avatar
Iustin Pop committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
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

462
  op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
463
  result = SubmitOpCode(op, opts=opts)
464 465
  if result:
    ToStderr(result)
Iustin Pop's avatar
Iustin Pop committed
466 467 468
  return 0


469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
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

489 490
  opcodelist = []
  if oob_command == constants.OOB_POWER_OFF:
491
    opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
492 493
                                              auto_promote=opts.auto_promote))

494 495
  opcodelist.append(opcodes.OpOobCommand(node_names=[node],
                                         command=oob_command))
496 497 498 499 500 501 502 503 504

  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]

505
  if result:
506 507 508 509
    (_, data_tuple) = result[0]
    if data_tuple[0] != constants.RS_NORMAL:
      if data_tuple[0] == constants.RS_UNAVAIL:
        result = "OOB is not supported"
510
      else:
511 512 513 514 515 516 517 518 519 520 521
        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)
522 523 524 525

  return constants.EXIT_SUCCESS


526 527 528
def ListVolumes(opts, args):
  """List logical volumes on node(s).

529 530 531 532 533 534 535 536
  @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

537
  """
538
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
539

540
  op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
541
  output = SubmitOpCode(op, opts=opts)
542 543

  if not opts.no_headers:
544 545 546 547 548 549
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

550
  unitfields = ["size"]
551 552 553

  numfields = ["size"]

554 555
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
556
                       numfields=numfields, data=output, units=opts.units)
557 558

  for line in data:
559
    ToStdout(line)
560 561 562 563

  return 0


Iustin Pop's avatar
Iustin Pop committed
564
def ListStorage(opts, args):
565 566 567 568 569 570 571 572 573 574 575
  """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

  """
576 577 578 579
  # 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

580
  storage_type = ConvertStorageType(opts.user_storage_type)
581

582
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
583

584
  op = opcodes.OpNodeQueryStorage(nodes=args,
585
                                  storage_type=storage_type,
586
                                  output_fields=selected_fields)
587
  output = SubmitOpCode(op, opts=opts)
588 589 590

  if not opts.no_headers:
    headers = {
591 592
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
593 594 595 596 597 598 599 600 601 602 603 604
      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]

605 606 607 608 609 610 611 612 613 614 615
  # 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)

616 617 618 619 620 621 622 623 624 625
  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
626
def ModifyStorage(opts, args):
627 628 629 630 631 632 633 634 635 636 637
  """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

638
  storage_type = ConvertStorageType(user_storage_type)
639 640 641 642

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
643
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
644 645

  if changes:
646
    op = opcodes.OpNodeModifyStorage(node_name=node_name,
647 648 649
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
650
    SubmitOpCode(op, opts=opts)
651 652
  else:
    ToStderr("No changes to perform, exiting.")
653 654


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


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

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

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


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