gnt_node.py 24.7 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 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
Iustin Pop's avatar
Iustin Pop committed
37 38


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

46

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


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


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


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


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

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

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

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


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

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


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

  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)


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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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


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

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

  dst_node = opts.dst_node
  iallocator = opts.iallocator

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

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

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


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

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

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

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

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


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

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

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

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


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

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

394
  for (name, primary_ip, secondary_ip, pinst, sinst,
395
       is_mc, drained, offline, master_capable, vm_capable) in result:
396 397 398
    ToStdout("Node name: %s", name)
    ToStdout("  primary ip: %s", primary_ip)
    ToStdout("  secondary ip: %s", secondary_ip)
399 400 401
    ToStdout("  master candidate: %s", is_mc)
    ToStdout("  drained: %s", drained)
    ToStdout("  offline: %s", offline)
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
    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
417 418 419 420 421

  return 0


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


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


460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
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

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
  opcodelist = []
  if oob_command == constants.OOB_POWER_OFF:
    opcodelist.append(opcodes.OpSetNodeParams(node_name=node, offline=True,
                                              auto_promote=opts.auto_promote))

  opcodelist.append(opcodes.OpOobCommand(node_name=node, command=oob_command))

  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]

495 496 497 498 499 500 501 502 503 504 505 506
  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


507 508 509
def ListVolumes(opts, args):
  """List logical volumes on node(s).

510 511 512 513 514 515 516 517
  @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

518
  """
519
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
520 521

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
522
  output = SubmitOpCode(op, opts=opts)
523 524

  if not opts.no_headers:
525 526 527 528 529 530
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

531
  unitfields = ["size"]
532 533 534

  numfields = ["size"]

535 536
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
537
                       numfields=numfields, data=output, units=opts.units)
538 539

  for line in data:
540
    ToStdout(line)
541 542 543 544

  return 0


Iustin Pop's avatar
Iustin Pop committed
545
def ListStorage(opts, args):
546 547 548 549 550 551 552 553 554 555 556
  """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

  """
557 558 559 560
  # 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

561
  storage_type = ConvertStorageType(opts.user_storage_type)
562

563
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
564 565

  op = opcodes.OpQueryNodeStorage(nodes=args,
566
                                  storage_type=storage_type,
567
                                  output_fields=selected_fields)
568
  output = SubmitOpCode(op, opts=opts)
569 570 571

  if not opts.no_headers:
    headers = {
572 573
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
574 575 576 577 578 579 580 581 582 583 584 585
      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]

586 587 588 589 590 591 592 593 594 595 596
  # 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)

597 598 599 600 601 602 603 604 605 606
  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
607
def ModifyStorage(opts, args):
608 609 610 611 612 613 614 615 616 617 618
  """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

619
  storage_type = ConvertStorageType(user_storage_type)
620 621 622 623

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
624
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
625 626 627 628 629 630

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
631
    SubmitOpCode(op, opts=opts)
632 633
  else:
    ToStderr("No changes to perform, exiting.")
634 635


Iustin Pop's avatar
Iustin Pop committed
636
def RepairStorage(opts, args):
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
  """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,
652 653
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
654
  SubmitOpCode(op, opts=opts)
655 656


Iustin Pop's avatar
Iustin Pop committed
657 658 659 660 661 662 663 664 665 666
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
667
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
668 669
                 opts.master_capable, opts.vm_capable, opts.secondary_ip,
                 opts.ndparams]
Iustin Pop's avatar
Iustin Pop committed
670
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
671 672 673 674
    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
675 676 677
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
678
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
679
                               vm_capable=opts.vm_capable,
680
                               secondary_ip=opts.secondary_ip,
681
                               force=opts.force,
682
                               ndparams=opts.ndparams,
683 684
                               auto_promote=opts.auto_promote,
                               powered=opts.node_powered)
Iustin Pop's avatar
Iustin Pop committed
685 686 687 688 689 690 691 692 693 694 695

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


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